summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt17
-rw-r--r--api/system-current.txt17
-rw-r--r--core/java/android/app/Activity.java18
-rw-r--r--core/java/android/app/ActivityManager.java29
-rw-r--r--core/java/android/app/ActivityManagerNative.java11
-rw-r--r--core/java/android/app/ActivityThread.java4
-rw-r--r--core/java/android/app/AssistContent.java6
-rw-r--r--core/java/android/app/ContextImpl.java3
-rw-r--r--core/java/android/app/IActivityManager.java2
-rw-r--r--core/java/android/app/Instrumentation.java19
-rw-r--r--core/java/android/app/VoiceInteractor.java96
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java17
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java11
-rw-r--r--core/java/android/bluetooth/BluetoothServerSocket.java6
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java14
-rw-r--r--core/java/android/content/Intent.java3
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java5
-rw-r--r--core/java/android/provider/Settings.java9
-rw-r--r--core/java/android/util/Log.java5
-rw-r--r--core/java/android/view/View.java18
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java6
-rw-r--r--core/java/android/webkit/WebView.java24
-rw-r--r--core/java/android/webkit/WebViewProvider.java4
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java1
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java4
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java20
-rw-r--r--core/java/com/android/internal/os/BluetoothPowerCalculator.java20
-rw-r--r--core/java/com/android/internal/os/WifiPowerCalculator.java8
-rw-r--r--core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java1
-rw-r--r--docs/html/training/wearables/watch-faces/drawing.jd209
-rw-r--r--keystore/java/android/security/KeyStore.java8
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java8
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java155
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java6
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java829
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java2
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSpi.java343
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java9
-rw-r--r--keystore/java/android/security/keystore/KeyProperties.java47
-rw-r--r--keystore/java/android/security/keystore/KeymasterUtils.java17
-rw-r--r--libs/hwui/DisplayListCanvas.cpp21
-rw-r--r--libs/hwui/DisplayListCanvas.h2
-rwxr-xr-xlibs/hwui/OpenGLRenderer.h16
-rw-r--r--media/java/android/media/MediaCodec.java47
-rw-r--r--media/java/android/media/midi/package.html63
-rw-r--r--media/jni/android_media_MediaCodec.cpp58
-rw-r--r--media/jni/android_media_MediaCodec.h7
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooter.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java10
-rw-r--r--services/core/java/com/android/server/AnyMotionDetector.java438
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java49
-rw-r--r--services/core/java/com/android/server/DeviceIdleController.java115
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java16
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java77
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java51
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java21
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java10
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java11
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkDiagnostics.java509
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java133
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java12
-rw-r--r--telecomm/java/android/telecom/Connection.java3
-rw-r--r--telecomm/java/android/telecom/InCallService.java7
-rw-r--r--telecomm/java/android/telecom/RemoteConnection.java165
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java4
-rw-r--r--telecomm/java/android/telecom/VideoProfile.java27
-rw-r--r--telephony/java/android/telephony/PhoneStateListener.java9
-rw-r--r--telephony/java/com/android/ims/ImsCallProfile.java2
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java13
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java54
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java6
-rw-r--r--tools/aapt2/Android.mk1
-rw-r--r--tools/aapt2/Main.cpp49
-rw-r--r--tools/aapt2/ProguardRules.cpp240
-rw-r--r--tools/aapt2/ProguardRules.h58
-rw-r--r--tools/aapt2/Source.h5
-rw-r--r--tools/aapt2/Util.cpp45
-rw-r--r--tools/aapt2/Util.h17
-rw-r--r--tools/aapt2/Util_test.cpp40
-rw-r--r--tools/aapt2/data/AndroidManifest.xml3
-rw-r--r--tools/aapt2/data/Makefile3
-rw-r--r--tools/aapt2/data/res/layout/main.xml3
95 files changed, 3926 insertions, 941 deletions
diff --git a/api/current.txt b/api/current.txt
index 4a3fafd8e0f1..f73d56fd24ca 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3449,6 +3449,7 @@ package android.app {
method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public void onProvideAssistContent(android.app.assist.AssistContent);
method public void onProvideAssistData(android.os.Bundle);
+ method public android.net.Uri onProvideReferrer();
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method protected void onRestart();
method protected void onRestoreInstanceState(android.os.Bundle);
@@ -5490,8 +5491,11 @@ package android.app {
field public static final int MODE_NIGHT_YES = 2; // 0x2
}
- public class VoiceInteractor {
+ public final class VoiceInteractor {
+ method public android.app.VoiceInteractor.Request getActiveRequest(java.lang.String);
+ method public android.app.VoiceInteractor.Request[] getActiveRequests();
method public boolean submitRequest(android.app.VoiceInteractor.Request);
+ method public boolean submitRequest(android.app.VoiceInteractor.Request, java.lang.String);
method public boolean[] supportsCommands(java.lang.String[]);
}
@@ -5554,6 +5558,7 @@ package android.app {
method public void cancel();
method public android.app.Activity getActivity();
method public android.content.Context getContext();
+ method public java.lang.String getName();
method public void onAttached(android.app.Activity);
method public void onCancel();
method public void onDetached();
@@ -30611,8 +30616,8 @@ package android.telecom {
field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
field public static final java.lang.String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
+ field public static final java.lang.String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
- field public static final java.lang.String EXTRA_INCOMING_CALL_HANDLE = "android.telecom.extra.INCOMING_CALL_HANDLE";
field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
@@ -39237,8 +39242,8 @@ package android.webkit {
method public deprecated void onGlobalFocusChanged(android.view.View, android.view.View);
method public void onPause();
method public void onResume();
- method public boolean overlayHorizontalScrollbar();
- method public boolean overlayVerticalScrollbar();
+ method public deprecated boolean overlayHorizontalScrollbar();
+ method public deprecated boolean overlayVerticalScrollbar();
method public boolean pageDown(boolean);
method public boolean pageUp(boolean);
method public void pauseTimers();
@@ -39258,13 +39263,13 @@ package android.webkit {
method public deprecated void setCertificate(android.net.http.SslCertificate);
method public void setDownloadListener(android.webkit.DownloadListener);
method public void setFindListener(android.webkit.WebView.FindListener);
- method public void setHorizontalScrollbarOverlay(boolean);
+ method public deprecated void setHorizontalScrollbarOverlay(boolean);
method public void setHttpAuthUsernamePassword(java.lang.String, java.lang.String, java.lang.String, java.lang.String);
method public void setInitialScale(int);
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
- method public void setVerticalScrollbarOverlay(boolean);
+ method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
method public void setWebViewClient(android.webkit.WebViewClient);
diff --git a/api/system-current.txt b/api/system-current.txt
index e4e8387ad4cd..e2d89a08f29e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3535,6 +3535,7 @@ package android.app {
method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
method public void onProvideAssistContent(android.app.assist.AssistContent);
method public void onProvideAssistData(android.os.Bundle);
+ method public android.net.Uri onProvideReferrer();
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method protected void onRestart();
method protected void onRestoreInstanceState(android.os.Bundle);
@@ -5586,8 +5587,11 @@ package android.app {
field public static final int MODE_NIGHT_YES = 2; // 0x2
}
- public class VoiceInteractor {
+ public final class VoiceInteractor {
+ method public android.app.VoiceInteractor.Request getActiveRequest(java.lang.String);
+ method public android.app.VoiceInteractor.Request[] getActiveRequests();
method public boolean submitRequest(android.app.VoiceInteractor.Request);
+ method public boolean submitRequest(android.app.VoiceInteractor.Request, java.lang.String);
method public boolean[] supportsCommands(java.lang.String[]);
}
@@ -5650,6 +5654,7 @@ package android.app {
method public void cancel();
method public android.app.Activity getActivity();
method public android.content.Context getContext();
+ method public java.lang.String getName();
method public void onAttached(android.app.Activity);
method public void onCancel();
method public void onDetached();
@@ -32825,8 +32830,8 @@ package android.telecom {
field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
field public static final java.lang.String EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME = "android.telecom.extra.CHANGE_DEFAULT_DIALER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
+ field public static final java.lang.String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
field public static final java.lang.String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
- field public static final java.lang.String EXTRA_INCOMING_CALL_HANDLE = "android.telecom.extra.INCOMING_CALL_HANDLE";
field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
@@ -41582,8 +41587,8 @@ package android.webkit {
method public deprecated void onGlobalFocusChanged(android.view.View, android.view.View);
method public void onPause();
method public void onResume();
- method public boolean overlayHorizontalScrollbar();
- method public boolean overlayVerticalScrollbar();
+ method public deprecated boolean overlayHorizontalScrollbar();
+ method public deprecated boolean overlayVerticalScrollbar();
method public boolean pageDown(boolean);
method public boolean pageUp(boolean);
method public void pauseTimers();
@@ -41603,13 +41608,13 @@ package android.webkit {
method public deprecated void setCertificate(android.net.http.SslCertificate);
method public void setDownloadListener(android.webkit.DownloadListener);
method public void setFindListener(android.webkit.WebView.FindListener);
- method public void setHorizontalScrollbarOverlay(boolean);
+ method public deprecated void setHorizontalScrollbarOverlay(boolean);
method public void setHttpAuthUsernamePassword(java.lang.String, java.lang.String, java.lang.String, java.lang.String);
method public void setInitialScale(int);
method public deprecated void setMapTrackballToArrowKeys(boolean);
method public void setNetworkAvailable(boolean);
method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
- method public void setVerticalScrollbarOverlay(boolean);
+ method public deprecated void setVerticalScrollbarOverlay(boolean);
method public void setWebChromeClient(android.webkit.WebChromeClient);
method public static void setWebContentsDebuggingEnabled(boolean);
method public void setWebViewClient(android.webkit.WebViewClient);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b88d5d47bbb1..1b4ee2ede395 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4281,6 +4281,10 @@ public class Activity extends ContextThemeWrapper
if (mParent == null) {
int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
try {
+ Uri referrer = onProvideReferrer();
+ if (referrer != null) {
+ intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+ }
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
result = ActivityManagerNative.getDefault()
@@ -4465,6 +4469,10 @@ public class Activity extends ContextThemeWrapper
@Override
public void startActivityForResult(
String who, Intent intent, int requestCode, @Nullable Bundle options) {
+ Uri referrer = onProvideReferrer();
+ if (referrer != null) {
+ intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+ }
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, who,
@@ -4618,6 +4626,16 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Override to generate the desired referrer for the content currently being shown
+ * by the app. The default implementation returns null, meaning the referrer will simply
+ * be the android-app: of the package name of this activity. Return a non-null Uri to
+ * have that supplied as the {@link Intent#EXTRA_REFERRER} of any activities started from it.
+ */
+ public Uri onProvideReferrer() {
+ return null;
+ }
+
+ /**
* Return the name of the package that invoked this activity. This is who
* the data in {@link #setResult setResult()} will be sent to. You can
* use this information to validate that the recipient is allowed to
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3892dd9f979d..da345a6a7647 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -279,51 +279,54 @@ public class ActivityManager {
* all activities that are visible to the user. */
public static final int PROCESS_STATE_TOP = 2;
+ /** @hide Process is hosting a foreground service due to a system binding. */
+ public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;
+
/** @hide Process is hosting a foreground service. */
- public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
/** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
- public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+ public static final int PROCESS_STATE_TOP_SLEEPING = 5;
/** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
/** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
/** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 7;
+ public static final int PROCESS_STATE_BACKUP = 8;
/** @hide Process is in the background, but it can't restore its state so we want
* to try to avoid killing it. */
- public static final int PROCESS_STATE_HEAVY_WEIGHT = 8;
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 9;
/** @hide Process is in the background running a service. Unlike oom_adj, this level
* is used for both the normal running in background state and the executing
* operations state. */
- public static final int PROCESS_STATE_SERVICE = 9;
+ public static final int PROCESS_STATE_SERVICE = 10;
/** @hide Process is in the background running a receiver. Note that from the
* perspective of oom_adj receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 10;
+ public static final int PROCESS_STATE_RECEIVER = 11;
/** @hide Process is in the background but hosts the home activity. */
- public static final int PROCESS_STATE_HOME = 11;
+ public static final int PROCESS_STATE_HOME = 12;
/** @hide Process is in the background but hosts the last shown activity. */
- public static final int PROCESS_STATE_LAST_ACTIVITY = 12;
+ public static final int PROCESS_STATE_LAST_ACTIVITY = 13;
/** @hide Process is being cached for later use and contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY = 13;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY = 14;
/** @hide Process is being cached for later use and is a client of another cached
* process that contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 14;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15;
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 15;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 16;
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index dabcc50cb3aa..6ae21eb2d25a 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2197,7 +2197,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle extras = data.readBundle();
AssistStructure structure = AssistStructure.CREATOR.createFromParcel(data);
AssistContent content = AssistContent.CREATOR.createFromParcel(data);
- reportAssistContextExtras(token, extras, structure, content);
+ Uri referrer = data.readInt() != 0 ? Uri.CREATOR.createFromParcel(data) : null;
+ reportAssistContextExtras(token, extras, structure, content, referrer);
reply.writeNoException();
return true;
}
@@ -5367,7 +5368,7 @@ class ActivityManagerProxy implements IActivityManager
}
public void reportAssistContextExtras(IBinder token, Bundle extras, AssistStructure structure,
- AssistContent content) throws RemoteException {
+ AssistContent content, Uri referrer) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -5375,6 +5376,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeBundle(extras);
structure.writeToParcel(data, 0);
content.writeToParcel(data, 0);
+ if (referrer != null) {
+ data.writeInt(1);
+ referrer.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(REPORT_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ffb3fb867f3d..e21c04a35d6a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2572,9 +2572,11 @@ public final class ActivityThread {
AssistStructure structure = null;
AssistContent content = new AssistContent();
ActivityClientRecord r = mActivities.get(cmd.activityToken);
+ Uri referrer = null;
if (r != null) {
r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
r.activity.onProvideAssistData(data);
+ referrer = r.activity.onProvideReferrer();
if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL) {
structure = new AssistStructure(r.activity);
Intent activityIntent = r.activity.getIntent();
@@ -2597,7 +2599,7 @@ public final class ActivityThread {
}
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
- mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content);
+ mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/AssistContent.java b/core/java/android/app/AssistContent.java
index d23e73dfe33f..173b23725baa 100644
--- a/core/java/android/app/AssistContent.java
+++ b/core/java/android/app/AssistContent.java
@@ -68,8 +68,10 @@ public class AssistContent {
setWebUri(null);
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
- if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
- setWebUri(uri);
+ if (uri != null) {
+ if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
+ setWebUri(uri);
+ }
}
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 829b0984ea45..be36af7dab16 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1745,7 +1745,8 @@ class ContextImpl extends Context {
mResourcesManager = ResourcesManager.getInstance();
final int displayId = (createDisplayWithId != Display.INVALID_DISPLAY)
- ? createDisplayWithId : getDisplayId();
+ ? createDisplayWithId
+ : (display != null) ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
CompatibilityInfo compatInfo = null;
if (container != null) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 0d5e1c75562e..9311e5e459a2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -436,7 +436,7 @@ public interface IActivityManager extends IInterface {
throws RemoteException;
public void reportAssistContextExtras(IBinder token, Bundle extras,
- AssistStructure structure, AssistContent content) throws RemoteException;
+ AssistStructure structure, AssistContent content, Uri referrer) throws RemoteException;
public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle,
Bundle args) throws RemoteException;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 0cc57bae54c7..653f1b661662 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -24,6 +24,7 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.input.InputManager;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
@@ -1439,7 +1440,7 @@ public class Instrumentation {
* objects and dispatches this call to the system activity manager; you can
* override this to watch for the application to start an activity, and
* modify what happens when it does.
- *
+ *
* <p>This method returns an {@link ActivityResult} object, which you can
* use when intercepting application calls to avoid performing the start
* activity action but still return the result the application is
@@ -1448,10 +1449,10 @@ public class Instrumentation {
* you would like the application to see, and don't call up to the super
* class. Note that an application is only expecting a result if
* <var>requestCode</var> is &gt;= 0.
- *
+ *
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
- *
+ *
* @param who The Context from which the activity is being started.
* @param contextThread The main thread of the Context from which the activity
* is being started.
@@ -1464,23 +1465,27 @@ public class Instrumentation {
* @param requestCode Identifier for this request's result; less than zero
* if the caller is not expecting a result.
* @param options Addition options.
- *
+ *
* @return To force the return of a particular result, return an
* ActivityResult object containing the desired data; otherwise
* return null. The default implementation always returns null.
- *
+ *
* @throws android.content.ActivityNotFoundException
- *
+ *
* @see Activity#startActivity(Intent)
* @see Activity#startActivityForResult(Intent, int)
* @see Activity#startActivityFromChild
- *
+ *
* {@hide}
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ Uri referrer = target != null ? target.onProvideReferrer() : null;
+ if (referrer != null) {
+ intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index eccd9dca820c..9cc399da2580 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -58,9 +58,11 @@ import java.util.ArrayList;
* request, rather than holding on to the activity instance yourself, either explicitly
* or implicitly through a non-static inner class.
*/
-public class VoiceInteractor {
+public final class VoiceInteractor {
static final String TAG = "VoiceInteractor";
- static final boolean DEBUG = true;
+ static final boolean DEBUG = false;
+
+ static final Request[] NO_REQUESTS = new Request[0];
final IVoiceInteractor mInteractor;
@@ -189,7 +191,7 @@ public class VoiceInteractor {
}
};
- final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
+ final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<>();
static final int MSG_CONFIRMATION_RESULT = 1;
static final int MSG_PICK_OPTION_RESULT = 2;
@@ -206,10 +208,22 @@ public class VoiceInteractor {
IVoiceInteractorRequest mRequestInterface;
Context mContext;
Activity mActivity;
+ String mName;
Request() {
}
+ /**
+ * Return the name this request was submitted through
+ * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Cancel this active request.
+ */
public void cancel() {
try {
mRequestInterface.cancel();
@@ -218,20 +232,39 @@ public class VoiceInteractor {
}
}
+ /**
+ * Return the current {@link Context} this request is associated with. May change
+ * if the activity hosting it goes through a configuration change.
+ */
public Context getContext() {
return mContext;
}
+ /**
+ * Return the current {@link Activity} this request is associated with. Will change
+ * if the activity is restarted such as through a configuration change.
+ */
public Activity getActivity() {
return mActivity;
}
+ /**
+ * Report from voice interaction service: this operation has been canceled, typically
+ * as a completion of a previous call to {@link #cancel}.
+ */
public void onCancel() {
}
+ /**
+ * The request is now attached to an activity, or being re-attached to a new activity
+ * after a configuration change.
+ */
public void onAttached(Activity activity) {
}
+ /**
+ * The request is being detached from an activity.
+ */
public void onDetached() {
}
@@ -239,6 +272,7 @@ public class VoiceInteractor {
mRequestInterface = null;
mContext = null;
mActivity = null;
+ mName = null;
}
abstract IVoiceInteractorRequest submit(IVoiceInteractor interactor,
@@ -761,12 +795,31 @@ public class VoiceInteractor {
}
public boolean submitRequest(Request request) {
+ return submitRequest(request, null);
+ }
+
+ /**
+ * Submit a new {@link Request} to the voice interaction service. The request must be
+ * one of the available subclasses -- {@link ConfirmationRequest}, {@link PickOptionRequest},
+ * {@link CompleteVoiceRequest}, {@link AbortVoiceRequest}, or {@link CommandRequest}.
+ *
+ * @param request The desired request to submit.
+ * @param name An optional name for this request, or null. This can be used later with
+ * {@link #getActiveRequests} and {@link #getActiveRequest} to find the request.
+ *
+ * @return Returns true of the request was successfully submitted, else false.
+ */
+ public boolean submitRequest(Request request, String name) {
try {
+ if (request.mRequestInterface != null) {
+ throw new IllegalStateException("Given " + request + " is already active");
+ }
IVoiceInteractorRequest ireq = request.submit(mInteractor,
mContext.getOpPackageName(), mCallback);
request.mRequestInterface = ireq;
request.mContext = mContext;
request.mActivity = mActivity;
+ request.mName = name;
synchronized (mActiveRequests) {
mActiveRequests.put(ireq.asBinder(), request);
}
@@ -778,6 +831,43 @@ public class VoiceInteractor {
}
/**
+ * Return all currently active requests.
+ */
+ public Request[] getActiveRequests() {
+ synchronized (mActiveRequests) {
+ final int N = mActiveRequests.size();
+ if (N <= 0) {
+ return NO_REQUESTS;
+ }
+ Request[] requests = new Request[N];
+ for (int i=0; i<N; i++) {
+ requests[i] = mActiveRequests.valueAt(i);
+ }
+ return requests;
+ }
+ }
+
+ /**
+ * Return any currently active request that was submitted with the given name.
+ *
+ * @param name The name used to submit the request, as per
+ * {@link #submitRequest(android.app.VoiceInteractor.Request, String)}.
+ * @return Returns the active request with that name, or null if there was none.
+ */
+ public Request getActiveRequest(String name) {
+ synchronized (mActiveRequests) {
+ final int N = mActiveRequests.size();
+ for (int i=0; i<N; i++) {
+ Request req = mActiveRequests.valueAt(i);
+ if (name == req.getName() || (name != null && name.equals(req.getName()))) {
+ return req;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Queries the supported commands available from the VoiceInteractionService.
* The command is a string that describes the generic operation to be performed.
* An example might be "org.example.commands.PICK_DATE" to ask the user to pick
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ab3f7bcdf563..97afafa4c8b7 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1467,7 +1467,7 @@ public final class BluetoothAdapter {
* @hide
*/
public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException {
- return listenUsingRfcommOn(channel, false);
+ return listenUsingRfcommOn(channel, false, false);
}
/**
@@ -1482,14 +1482,17 @@ public final class BluetoothAdapter {
* {@link SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number.
* @param channel RFCOMM channel to listen on
* @param mitm enforce man-in-the-middle protection for authentication.
+ * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2 connections.
* @return a listening RFCOMM BluetoothServerSocket
* @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions, or channel in use.
* @hide
*/
- public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm) throws IOException {
+ public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm,
+ boolean min16DigitPin)
+ throws IOException {
BluetoothServerSocket socket = new BluetoothServerSocket(
- BluetoothSocket.TYPE_RFCOMM, true, true, channel, mitm);
+ BluetoothSocket.TYPE_RFCOMM, true, true, channel, mitm, min16DigitPin);
int errno = socket.mSocket.bindListen();
if (channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
@@ -1694,14 +1697,16 @@ public final class BluetoothAdapter {
* {@link SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number.
* @param port the PSM to listen on
* @param mitm enforce man-in-the-middle protection for authentication.
+ * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2 connections.
* @return An L2CAP BluetoothServerSocket
* @throws IOException On error, for example Bluetooth not available, or
* insufficient permissions.
* @hide
*/
- public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm) throws IOException {
+ public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin)
+ throws IOException {
BluetoothServerSocket socket = new BluetoothServerSocket(
- BluetoothSocket.TYPE_L2CAP, true, true, port, mitm);
+ BluetoothSocket.TYPE_L2CAP, true, true, port, mitm, min16DigitPin);
int errno = socket.mSocket.bindListen();
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
@@ -1727,7 +1732,7 @@ public final class BluetoothAdapter {
* @hide
*/
public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
- return listenUsingL2capOn(port, false);
+ return listenUsingL2capOn(port, false, false);
}
/**
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index dcf06d8a8875..c96fe71e7974 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -531,6 +531,13 @@ public final class BluetoothDevice implements Parcelable {
public static final int PAIRING_VARIANT_OOB_CONSENT = 6;
/**
+ * The user will be prompted to enter a 16 digit pin or
+ * an app will enter a 16 digit pin for user.
+ * @hide
+ */
+ public static final int PAIRING_VARIANT_PIN_16_DIGITS = 7;
+
+ /**
* Used as an extra field in {@link #ACTION_UUID} intents,
* Contains the {@link android.os.ParcelUuid}s of the remote device which
* is a parcelable version of {@link UUID}.
@@ -1315,8 +1322,8 @@ public final class BluetoothDevice implements Parcelable {
Log.e(TAG, "", e);
}
return false;
- }
-
+ }
+
/**
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
* outgoing connection to this remote device on given channel.
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index a80f55c0e497..c15852dcdaa7 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -98,14 +98,16 @@ public final class BluetoothServerSocket implements Closeable {
* @param encrypt require the connection to be encrypted
* @param port remote port
* @param mitm enforce man-in-the-middle protection for authentication.
+ * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection
* @throws IOException On error, for example Bluetooth not available, or
* insufficient privileges
*/
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port,
- boolean mitm)
+ boolean mitm, boolean min16DigitPin)
throws IOException {
mChannel = port;
- mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null, mitm);
+ mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null, mitm,
+ min16DigitPin);
if(port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
mSocket.setExcludeSdp(true);
}
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 6ca6976b9ae2..6302521e6341 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -107,6 +107,7 @@ public final class BluetoothSocket implements Closeable {
/*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
/*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2;
/*package*/ static final int SEC_FLAG_AUTH_MITM = 1 << 3;
+ /*package*/ static final int SEC_FLAG_AUTH_16_DIGIT = 1 << 4;
private final int mType; /* one of TYPE_RFCOMM etc */
private BluetoothDevice mDevice; /* remote device */
@@ -118,6 +119,7 @@ public final class BluetoothSocket implements Closeable {
private final ParcelUuid mUuid;
private boolean mExcludeSdp = false; /* when true no SPP SDP record will be created */
private boolean mAuthMitm = false; /* when true Man-in-the-middle protection will be enabled*/
+ private boolean mMin16DigitPin = false; /* Minimum 16 digit pin for sec mode 2 connections */
private ParcelFileDescriptor mPfd;
private LocalSocket mSocket;
private InputStream mSocketIS;
@@ -160,7 +162,7 @@ public final class BluetoothSocket implements Closeable {
*/
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
- this(type, fd, auth, encrypt, device, port, uuid, false);
+ this(type, fd, auth, encrypt, device, port, uuid, false, false);
}
/**
@@ -173,11 +175,13 @@ public final class BluetoothSocket implements Closeable {
* @param port remote port
* @param uuid SDP uuid
* @param mitm enforce man-in-the-middle protection.
+ * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection
* @throws IOException On error, for example Bluetooth not available, or
* insufficient privileges
*/
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
- BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm) throws IOException {
+ BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm, boolean min16DigitPin)
+ throws IOException {
if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type);
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1
&& port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
@@ -191,6 +195,7 @@ public final class BluetoothSocket implements Closeable {
mType = type;
mAuth = auth;
mAuthMitm = mitm;
+ mMin16DigitPin = min16DigitPin;
mEncrypt = encrypt;
mDevice = device;
mPort = port;
@@ -223,6 +228,7 @@ public final class BluetoothSocket implements Closeable {
mServiceName = s.mServiceName;
mExcludeSdp = s.mExcludeSdp;
mAuthMitm = s.mAuthMitm;
+ mMin16DigitPin = s.mMin16DigitPin;
}
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
BluetoothSocket as = new BluetoothSocket(this);
@@ -254,7 +260,7 @@ public final class BluetoothSocket implements Closeable {
*/
private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
int port) throws IOException {
- this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null, false);
+ this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null, false, false);
}
/** @hide */
@@ -276,6 +282,8 @@ public final class BluetoothSocket implements Closeable {
flags |= BTSOCK_FLAG_NO_SDP;
if(mAuthMitm)
flags |= SEC_FLAG_AUTH_MITM;
+ if(mMin16DigitPin)
+ flags |= SEC_FLAG_AUTH_16_DIGIT;
return flags;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5c23204aeeba..25be96afe59b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1226,7 +1226,8 @@ public class Intent implements Parcelable, Cloneable {
* <p>
* Input: {@link #EXTRA_ASSIST_PACKAGE}, {@link #EXTRA_ASSIST_CONTEXT}, can provide
* additional optional contextual information about where the user was when they
- * requested the assist.
+ * requested the assist; {@link #EXTRA_REFERRER} may be set with additional referrer
+ * information.
* Output: nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index e61813cc2c94..82d3e0ac50bd 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -396,6 +396,11 @@ public class FingerprintManager {
* @param flags optional flags; should be 0
* @param callback an object to receive authentication events
* @param handler an optional handler to handle callback events
+ *
+ * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
+ * by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
+ * facility</a>.
+ * @throws IllegalStateException if the crypto primitive is not initialized.
*/
@RequiresPermission(USE_FINGERPRINT)
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cac4a53ed229..640f4348c64b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5549,6 +5549,15 @@ public final class Settings {
public static final String DOUBLE_TAP_TO_WAKE = "double_tap_to_wake";
/**
+ * The current assistant component. It could be a voice interaction service,
+ * or an activity that handles ACTION_ASSIST, or empty which means using the default
+ * handling.
+ *
+ * @hide
+ */
+ public static final String ASSISTANT = "assistant";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 121485a1dfbc..fe4193224950 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -289,7 +289,10 @@ public final class Log {
static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
boolean system) {
TerribleFailure what = new TerribleFailure(msg, tr);
- int bytes = println_native(logId, ASSERT, tag, msg + '\n'
+ // Only mark this as ERROR, do not use ASSERT since that should be
+ // reserved for cases where the system is guaranteed to abort.
+ // The onTerribleFailure call does not always cause a crash.
+ int bytes = println_native(logId, ERROR, tag, msg + '\n'
+ getStackTraceString(localStack ? what : tr));
sWtfHandler.onTerribleFailure(tag, what, system);
return bytes;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5dd5ab813fbd..be372d0bf859 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16070,23 +16070,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
} else if (cache != null) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- Paint cachePaint;
- int restoreAlpha = 0;
-
if (layerType == LAYER_TYPE_NONE) {
- cachePaint = parent.mCachePaint;
+ // no layer paint, use temporary paint to draw bitmap
+ Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
+ cachePaint.setAlpha((int) (alpha * 255));
+ canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
- cachePaint = mLayerPaint;
- restoreAlpha = mLayerPaint.getAlpha();
+ // use layer paint to draw the bitmap, merging the two alphas, but also restore
+ int layerPaintAlpha = mLayerPaint.getAlpha();
+ mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
+ canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
+ mLayerPaint.setAlpha(layerPaintAlpha);
}
- cachePaint.setAlpha((int) (alpha * 255));
- canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
- cachePaint.setAlpha(restoreAlpha);
}
if (restoreTo >= 0) {
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 687f9d064d72..76c8fbd4b295 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -304,13 +304,11 @@ public class EditorInfo implements InputType, Parcelable {
* name. The system had not verified the consistency between the package name here and
* application's uid. Consider to use {@link InputBinding#getUid()}, which is trustworthy.
* Starting from Android MNC, the system verifies the consistency between this package name
- * and application uid before {@link EditorInfo} is passed to the input method as long as the
- * application sets a non-empty package name.</p>
+ * and application uid before {@link EditorInfo} is passed to the input method.</p>
*
* <p><strong>Editor authors:</strong> Starting from Android MNC, the application is no longer
* able to establish input connections if the package name provided here is inconsistent with
- * application's uid. Note that the system does accept an empty string for an arbitrary
- * application uid.</p>
+ * application's uid.</p>
*/
public String packageName;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5080fccbf00d..aa72eb3cf2dd 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -611,41 +611,45 @@ public class WebView extends AbsoluteLayout
/**
* Specifies whether the horizontal scrollbar has overlay style.
*
+ * @deprecated This method has no effect.
* @param overlay true if horizontal scrollbar should have overlay style
*/
+ @Deprecated
public void setHorizontalScrollbarOverlay(boolean overlay) {
- checkThread();
- mProvider.setHorizontalScrollbarOverlay(overlay);
}
/**
* Specifies whether the vertical scrollbar has overlay style.
*
+ * @deprecated This method has no effect.
* @param overlay true if vertical scrollbar should have overlay style
*/
+ @Deprecated
public void setVerticalScrollbarOverlay(boolean overlay) {
- checkThread();
- mProvider.setVerticalScrollbarOverlay(overlay);
}
/**
* Gets whether horizontal scrollbar has overlay style.
*
- * @return true if horizontal scrollbar has overlay style
+ * @deprecated This method is now obsolete.
+ * @return true
*/
+ @Deprecated
public boolean overlayHorizontalScrollbar() {
- checkThread();
- return mProvider.overlayHorizontalScrollbar();
+ // The old implementation defaulted to true, so return true for consistency
+ return true;
}
/**
* Gets whether vertical scrollbar has overlay style.
*
- * @return true if vertical scrollbar has overlay style
+ * @deprecated This method is now obsolete.
+ * @return false
*/
+ @Deprecated
public boolean overlayVerticalScrollbar() {
- checkThread();
- return mProvider.overlayVerticalScrollbar();
+ // The old implementation defaulted to false, so return false for consistency
+ return false;
}
/**
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 09afcf1222c5..27033ad61186 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -70,12 +70,16 @@ public interface WebViewProvider {
public void init(Map<String, Object> javaScriptInterfaces,
boolean privateBrowsing);
+ // Deprecated - should never be called
public void setHorizontalScrollbarOverlay(boolean overlay);
+ // Deprecated - should never be called
public void setVerticalScrollbarOverlay(boolean overlay);
+ // Deprecated - should never be called
public boolean overlayHorizontalScrollbar();
+ // Deprecated - should never be called
public boolean overlayVerticalScrollbar();
public int getVisibleTitleHeight();
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index fe79effc6ab7..27aec4e6103a 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -140,6 +140,7 @@ public final class ProcessStats implements Parcelable {
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
+ STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index e6165a104481..4290e22e0b84 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -354,9 +354,9 @@ public final class BatteryStatsHelper {
if (mBluetoothPowerCalculator == null) {
if (checkHasBluetoothPowerReporting(mStats, mPowerProfile)) {
- mBluetoothPowerCalculator = new BluetoothPowerCalculator();
+ mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
} else {
- mBluetoothPowerCalculator = new BluetoothPowerCalculator();
+ mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
}
}
mBluetoothPowerCalculator.reset();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ee7ed0b7f5ee..087db78edbb2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -7814,11 +7814,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
info.getControllerIdleTimeMillis());
- final double opVoltage = mPowerProfile.getAveragePower(
- PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE);
- if (opVoltage != 0) {
+ // POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
+ final double opVolt = mPowerProfile.getAveragePower(
+ PowerProfile.POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+ if (opVolt != 0) {
+ // We store the power drain as mAms.
mWifiActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked(
- (long)(info.getControllerEnergyUsed() / opVoltage));
+ (long)(info.getControllerEnergyUsed() / opVolt));
}
}
}
@@ -7908,11 +7910,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
info.getControllerIdleTimeMillis());
- final double opVoltage = mPowerProfile.getAveragePower(
- PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE);
- if (opVoltage != 0) {
+ // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
+ final double opVolt = mPowerProfile.getAveragePower(
+ PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE) / 1000.0;
+ if (opVolt != 0) {
+ // We store the power drain as mAms.
mBluetoothActivityCounters[CONTROLLER_POWER_DRAIN].addCountLocked(
- (long) (info.getControllerEnergyUsed() / opVoltage));
+ (long) (info.getControllerEnergyUsed() / opVolt));
}
}
}
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 3557209f149a..1f5967283219 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -21,6 +21,15 @@ import android.util.Log;
public class BluetoothPowerCalculator extends PowerCalculator {
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private static final String TAG = "BluetoothPowerCalculator";
+ private final double mIdleMa;
+ private final double mRxMa;
+ private final double mTxMa;
+
+ public BluetoothPowerCalculator(PowerProfile profile) {
+ mIdleMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE);
+ mRxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX);
+ mTxMa = profile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX);
+ }
@Override
public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
@@ -37,10 +46,15 @@ public class BluetoothPowerCalculator extends PowerCalculator {
BatteryStats.CONTROLLER_TX_TIME, statsType);
final long rxTimeMs = stats.getBluetoothControllerActivity(
BatteryStats.CONTROLLER_RX_TIME, statsType);
- final long powerMaMs = stats.getBluetoothControllerActivity(
- BatteryStats.CONTROLLER_POWER_DRAIN, statsType);
- final double powerMah = powerMaMs / (double)(1000*60*60);
final long totalTimeMs = idleTimeMs + txTimeMs + rxTimeMs;
+ double powerMah = stats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_POWER_DRAIN, statsType) / (double)(1000*60*60);
+
+ if (powerMah == 0) {
+ // Some devices do not report the power, so calculate it.
+ powerMah = ((idleTimeMs * mIdleMa) + (rxTimeMs * mRxMa) + (txTimeMs * mTxMa))
+ / (1000*60*60);
+ }
if (DEBUG && powerMah != 0) {
Log.d(TAG, "Bluetooth active: time=" + (totalTimeMs)
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 961b0df61596..da98a6797322 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -70,14 +70,14 @@ public class WifiPowerCalculator extends PowerCalculator {
statsType);
app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs;
- double powerDrain = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN,
+ double powerDrainMah = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN,
statsType) / (double)(1000*60*60);
- if (powerDrain == 0) {
+ if (powerDrainMah == 0) {
// Some controllers do not report power drain, so we can calculate it here.
- powerDrain = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
+ powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
+ (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
}
- app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain);
+ app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain);
if (DEBUG) {
Log.d(TAG, "left over WiFi power: " + BatteryStatsHelper.makemAh(app.wifiPowerMah));
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index 8fbd2147c9d0..0d9980ad2059 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -170,6 +170,7 @@ public class BluetoothTestUtils extends Assert {
assertNotSame(-1, varient);
switch (varient) {
case BluetoothDevice.PAIRING_VARIANT_PIN:
+ case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
mDevice.setPin(mPin);
break;
case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
diff --git a/docs/html/training/wearables/watch-faces/drawing.jd b/docs/html/training/wearables/watch-faces/drawing.jd
index 60da5d50bc69..8b6de761e144 100644
--- a/docs/html/training/wearables/watch-faces/drawing.jd
+++ b/docs/html/training/wearables/watch-faces/drawing.jd
@@ -16,17 +16,20 @@ page.title=Drawing Watch Faces
<ul>
<li><a href="{@docRoot}design/wear/watchfaces.html">Watch Faces for Android Wear</a></li>
</ul>
+<h2>Related Samples</h2>
+ <ul>
+ <li><a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a></li>
+ </ul>
</div>
</div>
<p>After you have configured your project and added a class that implements the watch
face service, you can start writing code to initialize and draw your custom watch face.</p>
-<p>This lesson explains how the system invokes the methods in the
-watch face service using examples from the <em>WatchFace</em> sample
-included in the Android SDK. This sample is located in the
-<code>android-sdk/samples/android-21/wearable/WatchFace</code> directory. Many aspects of the
-service implementations described here (such as initialization and detecting device features)
+<p>This lesson includes examples from the
+<a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a> sample to show how the system uses
+the watch face service. Many aspects of the
+service implementations described here (such as initialization and device features detection)
apply to any watch face, so you can reuse some of the code in your own watch faces.</p>
@@ -36,7 +39,8 @@ apply to any watch face, so you can reuse some of the code in your own watch fac
width="180" height="180" alt="" style="margin-left:25px;margin-top:12px"/>
<p class="img-caption">
<strong>Figure 1.</strong> The analog and digital watch faces in
-the <em>WatchFace</em> sample.</p>
+the
+<a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a> sample.</p>
<h2 id="Initialize">Initialize Your Watch Face</h2>
@@ -51,8 +55,12 @@ of your watch face and makes it easier to maintain your code.</p>
<ol>
<li>Declare variables for a custom timer, graphic objects, and other elements.</li>
-<li>Initialize the watch face elements in the <code>Engine.onCreate()</code> method.</li>
-<li>Initialize the custom timer in the <code>Engine.onVisibilityChanged()</code> method.</li>
+<li>Initialize the watch face elements in the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onCreate(android.view.SurfaceHolder)"><code>Engine.onCreate()</code></a>
+method.</li>
+<li>Initialize the custom timer in the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>Engine.onVisibilityChanged()</code></a>
+method.</li>
</ol>
<p>The following sections describe these steps in detail.</p>
@@ -61,7 +69,8 @@ of your watch face and makes it easier to maintain your code.</p>
<p>The resources that you intialize when the system loads your service need to be accessible
at different points throughout your implementation, so you can reuse them. You achieve this
-by declaring member variables for these resources in your <code>WatchFaceService.Engine</code>
+by declaring member variables for these resources in your
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html"><code>WatchFaceService.Engine</code></a>
implementation.</p>
<p>Declare variables for the following elements:</p>
@@ -83,31 +92,25 @@ Your service implementation must register a broadcast receiver that is notified
zone changes and update the time accordingly.</dd>
</dl>
-<p>The <code>AnalogWatchFaceService.Engine</code> class in the <em>WatchFace</em> sample defines
-these variables as shown in the snippet below. The custom timer is implemented as a
-{@link android.os.Handler} instance that sends and processes delayed messages using the thread's
-message queue. For this particular watch face, the custom timer ticks once every second. When the
-timer ticks, the handler calls the <code>invalidate()</code> method and the system then calls
-the <code>onDraw()</code> method to redraw the watch face.</p>
+<p>The following snippet shows how to define these variables:</p>
<pre>
private class Engine extends CanvasWatchFaceService.Engine {
static final int MSG_UPDATE_TIME = 0;
- /* a time object */
- Time mTime;
+ Calendar mCalendar;
- /* device features */
+ // device features
boolean mLowBitAmbient;
- /* graphic objects */
+ // graphic objects
Bitmap mBackgroundBitmap;
Bitmap mBackgroundScaledBitmap;
Paint mHourPaint;
Paint mMinutePaint;
...
- /* handler to update the time once a second in interactive mode */
+ // handler to update the time once a second in interactive mode
final Handler mUpdateTimeHandler = new Handler() {
&#64;Override
public void handleMessage(Message message) {
@@ -126,53 +129,63 @@ private class Engine extends CanvasWatchFaceService.Engine {
}
};
- /* receiver to update the time zone */
+ // receiver to update the time zone
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
&#64;Override
public void onReceive(Context context, Intent intent) {
- mTime.clear(intent.getStringExtra("time-zone"));
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
+ invalidate();
}
};
- /* service methods (see other sections) */
+ // service methods (see other sections)
...
}
</pre>
+<p>In the example above, the custom timer is implemented as a
+{@link android.os.Handler} instance that sends and processes delayed messages using the thread's
+message queue. For this particular watch face, the custom timer ticks once every second. When the
+timer ticks, the handler calls the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#invalidate()"><code>invalidate()</code></a>
+method and the system then calls the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"<code>onDraw()</code></a>
+method to redraw the watch face.</p>
+
<h3 id="InitializeElements">Initialize watch face elements</h3>
-<p>After you have declared member variables for bitmap resources, paint styles, and other
-elements that you reuse every time your redraw your watch face, initialize them when the system
+<p>After declaring member variables for bitmap resources, paint styles, and other
+elements that you reuse every time you redraw your watch face, initialize them when the system
loads your service. Initializing these elements only once and reusing them improves performance
and battery life.</p>
-<p>In the <code>Engine.onCreate()</code> method, initialize the following elements:</p>
+<p>In the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onCreate(android.view.SurfaceHolder)"><code>Engine.onCreate()</code></a>
+method, initialize the following elements:</p>
<ul>
<li>Load the background image.</li>
<li>Create styles and colors to draw graphic objects.</li>
-<li>Allocate an object to hold the time.</li>
+<li>Allocate an object to calculate the time.</li>
<li>Configure the system UI.</li>
</ul>
-<p>The <code>Engine.onCreate()</code> method in the <code>AnalogWatchFaceService</code> class
-initializes these elements as follows:</p>
+<p>The following snippet shows how to initialize these elements:</p>
<pre>
&#64;Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
- /* configure the system UI (see next section) */
+ // configure the system UI (see next section)
...
- /* load the background image */
+ // load the background image
Resources resources = AnalogWatchFaceService.this.getResources();
- Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+ Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null);
mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
- /* create graphic styles */
+ // create graphic styles
mHourPaint = new Paint();
mHourPaint.setARGB(255, 200, 200, 200);
mHourPaint.setStrokeWidth(5.0f);
@@ -180,15 +193,16 @@ public void onCreate(SurfaceHolder holder) {
mHourPaint.setStrokeCap(Paint.Cap.ROUND);
...
- /* allocate an object to hold the time */
- mTime = new Time();
+ // allocate a Calendar to calculate local time using the UTC time and time zone
+ mCalendar = Calendar.getInstance();
}
</pre>
<p>The background bitmap is loaded only once when the system initializes the watch face. The
-graphic styles are instances of the {@link android.graphics.Paint} class. You later use these
-styles to draw the elements of your watch face inside the <code>Engine.onDraw()</code> method,
-as described in <a href="#Drawing">Drawing Your Watch Face</a>.</p>
+graphic styles are instances of the {@link android.graphics.Paint} class. Use these
+styles to draw the elements of your watch face inside the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>Engine.onDraw()</code></a>
+method, as described in <a href="#Drawing">Drawing Your Watch Face</a>.</p>
<h3 id="Timer">Initialize the custom timer</h3>
@@ -203,8 +217,8 @@ face in ambient mode</a>.</p>
<p>An example timer definition from the <code>AnalogWatchFaceService</code> class that ticks once
every second is shown in <a href="#Variables">Declare variables</a>. In the
-<code>Engine.onVisibilityChanged()</code> method, start the custom timer if these two
-conditions apply:</p>
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>Engine.onVisibilityChanged()</code></a>
+method, start the custom timer if these two conditions apply:</p>
<ul>
<li>The watch face is visible.</li>
@@ -230,8 +244,10 @@ private boolean shouldTimerBeRunning() {
<p>This custom timer ticks once every second, as described in <a href="#Variables">Declare
variables</a>.</p>
-<p>In the <code>Engine.onVisibilityChanged()</code> method, start the timer if required and
-and register the receiver for time zone changes as follows:</p>
+<p>In the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>onVisibilityChanged()</code></a>
+method, start the timer if required and register the receiver for time zone changes as follows:
+</p>
<pre>
&#64;Override
@@ -242,23 +258,24 @@ public void onVisibilityChanged(boolean visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
- mTime.clear(TimeZone.getDefault().getID());
- mTime.setToNow();
+ mCalendar.setTimeZone(TimeZone.getDefault());
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible and
- // whether we're in ambient mode), so we may need to start or stop the timer
+ // whether we're in ambient mode, so we may need to start or stop the timer
updateTimer();
}
</pre>
-<p>When the watch face is visible, the <code>onVisibilityChanged()</code> method registers
-the receiver for time zone changes and starts the custom timer if the device is in interactive
-mode. When the watch face is not visible, this method stops the custom timer and unregisters
-the receiver for time zone changes. The <code>registerReceiver()</code> and
-<code>unregisterReceiver()</code> methods are implemented as follows:</p>
+<p>When the watch face is visible, the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onVisibilityChanged(boolean)"><code>onVisibilityChanged()</code></a>
+method registers the receiver for time zone changes. If the device is in interactive mode, this
+method also starts the custom timer. When the watch face is not visible, this
+method stops the custom timer and unregisters the receiver for time zone changes.
+The <code>registerReceiver()</code> and <code>unregisterReceiver()</code> methods are implemented as
+follows:</p>
<pre>
private void registerReceiver() {
@@ -283,13 +300,16 @@ private void unregisterReceiver() {
<h3 id="TimeTick">Update the watch face in ambient mode</h3>
-<p>In ambient mode, the system calls the <code>Engine.onTimeTick()</code> method every minute.
-It is usually sufficient to update your watch face once per minute in this mode. To update your
-watch face while in interactive mode, you must provide a custom timer as described in
-<a href="#Timer">Initialize the custom timer</a>.</p>
+<p>In ambient mode, the system calls the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onTimeTick()"><code>Engine.onTimeTick()</code></a>
+method every minute. It is usually sufficient to update your watch face once per minute in this
+mode. To update your watch face while in interactive mode, you must provide a custom timer as
+described in <a href="#Timer">Initialize the custom timer</a>.</p>
<p>In ambient mode, most watch face implementations simply invalidate the canvas to redraw the watch
-face in the <code>Engine.onTimeTick()</code> method:</p>
+face in the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onTimeTick()"<code>Engine.onTimeTick()</code></a>
+method:</p>
<pre>
&#64;Override
@@ -320,8 +340,11 @@ face is active:</p>
<li>Specify the positioning of the system indicators.</li>
</ul>
-<p>To configure these aspects of the system UI, create a <code>WatchFaceStyle</code> instance
-and pass it to the <code>Engine.setWatchFaceStyle()</code> method.</p>
+<p>To configure these aspects of the system UI, create a
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceStyle.html"><code>WatchFaceStyle</code></a>
+instance and pass it to the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#setWatchFaceStyle(android.support.wearable.watchface.WatchFaceStyle)"><code>Engine.setWatchFaceStyle()</code></a>
+method.</p>
<p>The <code>AnalogWatchFaceService</code> class configures the system UI as follows:</p>
@@ -330,7 +353,7 @@ and pass it to the <code>Engine.setWatchFaceStyle()</code> method.</p>
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
- /* configure the system UI */
+ // configure the system UI
setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setBackgroundVisibility(WatchFaceStyle
@@ -350,16 +373,16 @@ For example, if the user selects a white background, you can add background prot
system indicators.</p>
<p>For more details about configuring the system UI, see the
-<a href="{@docRoot}shareables/training/wearable-support-docs.zip">API reference</a> for the
-<code>WatchFaceStyle</code> class.</p>
-
+<a href="{@docRoot}reference/packages-wearable-support.html">Wear API reference documentation</a>.
+</p>
<h2 id="Screen">Obtain Information About the Device Screen</h2>
-<p>The system calls the <code>Engine.onPropertiesChanged()</code> method when it determines
-the properties of the device screen, such as whether the device uses low-bit ambient mode and
-whether the screen requires burn-in protection.</p>
+<p>The system calls the
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onPropertiesChanged(android.os.Bundle)"><code>Engine.onPropertiesChanged()</code></a>
+method when it determines the properties of the device screen, such as whether the device uses
+low-bit ambient mode and whether the screen requires burn-in protection.</p>
<p>The following code snippet shows how to obtain these properties:</p>
@@ -394,12 +417,13 @@ Filtering</a>.</p>
<h2 id="Modes">Respond to Changes Between Modes</h2>
<p>When the device switches between ambient and interactive modes, the system calls the
-<code>Engine.onAmbientModeChanged()</code> method. Your service implementation should make
-any necessary adjustments to switch between modes and then call the <code>invalidate()</code>
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onAmbientModeChanged(boolean)"><code>Engine.onAmbientModeChanged()</code></a>
+method. Your service implementation should make any necessary adjustments to switch between modes
+and then call the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#invalidate()"><code>invalidate()</code></a>
method for the system to redraw the watch face.</p>
-<p>The following snippet shows how this method is implemented in the
-<code>AnalogWatchFaceService</code> class inside the <em>WatchFace</em> sample:</p>
+<p>The following snippet shows how to implement this method:</p>
<pre>
&#64;Override
@@ -426,28 +450,34 @@ system can redraw the watch face.</p>
<h2 id="Drawing">Draw Your Watch Face</h2>
-<p>To draw a custom watch face, the system calls the <code>Engine.onDraw()</code> method with a
-{@link android.graphics.Canvas} instance and the bounds in which you should draw your watch face.
-The bounds account for any inset areas, such as the "chin" on the bottom of some round devices.
-You can use this canvas to draw your watch face directly as follows:</p>
+<p>To draw a custom watch face, the system calls the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>Engine.onDraw()</code></a>
+method with a {@link android.graphics.Canvas} instance and the bounds in which you should draw your
+watch face. The bounds take into account any inset areas, such as the "chin" on the bottom of some
+round devices. You can use this canvas to draw your watch face directly as follows:</p>
<ol>
-<li>If this is the first invocation of the <code>onDraw()</code> method, scale your background
-to fit.</li>
+<li>If this is the first invocation of the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>onDraw()</code></a>
+method, scale your background to fit.</li>
<li>Check whether the device is in ambient mode or interactive mode.</li>
<li>Perform any required graphic computations.</li>
<li>Draw your background bitmap on the canvas.</li>
<li>Use the methods in the {@link android.graphics.Canvas} class to draw your watch face.</li>
</ol>
-<p>The <code>AnalogWatchFaceService</code> class in the <em>WatchFace</em> sample follows these
-steps to implement the <code>onDraw()</code> method as follows:</p>
+<p>The following snippet shows how to implement the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>onDraw()</code></a>
+method:</p>
<pre>
&#64;Override
public void onDraw(Canvas canvas, Rect bounds) {
// Update the time
- mTime.setToNow();
+ mCalendar.setTimeInMillis(System.currentTimeMillis());
+
+ // Constant to help calculate clock hand rotations
+ final float TWO_PI = (float) Math.PI * 2f;
int width = bounds.width();
int height = bounds.height();
@@ -457,7 +487,7 @@ public void onDraw(Canvas canvas, Rect bounds) {
|| mBackgroundScaledBitmap.getWidth() != width
|| mBackgroundScaledBitmap.getHeight() != height) {
mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
- width, height, true /* filter */);
+ width, height, true);
}
canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
@@ -468,10 +498,13 @@ public void onDraw(Canvas canvas, Rect bounds) {
float centerY = height / 2f;
// Compute rotations and lengths for the clock hands.
- float secRot = mTime.second / 30f * (float) Math.PI;
- int minutes = mTime.minute;
- float minRot = minutes / 30f * (float) Math.PI;
- float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+ float seconds = mCalendar.get(Calendar.SECOND) +
+ mCalendar.get(Calendar.MILLISECOND) / 1000f;
+ float secRot = seconds / 60f * TWO_PI;
+ float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
+ float minRot = minutes / 60f * TWO_PI;
+ float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
+ float hrRot = hours / 12f * TWO_PI;
float secLength = centerX - 20;
float minLength = centerX - 40;
@@ -499,11 +532,13 @@ public void onDraw(Canvas canvas, Rect bounds) {
<p>This method computes the required positions for the clock hands based on the current time
and draws them on top of the background bitmap using the graphic styles initialized in the
-<code>onCreate()</code> method. The second hand is only drawn in interactive mode, not in
-ambient mode.</p>
+<a href="{@docRoot}reference/android/support/wearable/watchface/WatchFaceService.Engine.html#onCreate(android.view.SurfaceHolder)"><code>onCreate()</code></a>
+method. The second hand is only drawn in interactive mode, not in ambient mode.</p>
<p>For more information about drawing on a Canvas instance, see <a
href="{@docRoot}guide/topics/graphics/2d-graphics.html">Canvas and Drawables</a>.</p>
-<p>The <em>WatchFace</em> sample in the Android SDK includes additional watch faces that you
-can refer to as examples of how to implement the <code>onDraw()</code> method.</p>
+<p>The <a href="{@docRoot}samples/WatchFace/index.html">WatchFace</a> sample includes additional
+watch faces that you can refer to as examples of how to implement the
+<a href="{@docRoot}reference/android/support/wearable/watchface/CanvasWatchFaceService.Engine.html#onDraw(android.graphics.Canvas, android.graphics.Rect)"><code>onDraw()</code></a>
+method.</p>
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index ad348f8fdf75..893771aab987 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -181,11 +181,15 @@ public class KeyStore {
}
public boolean put(String key, byte[] value, int uid, int flags) {
+ return insert(key, value, uid, flags) == NO_ERROR;
+ }
+
+ public int insert(String key, byte[] value, int uid, int flags) {
try {
- return mBinder.insert(key, value, uid, flags) == NO_ERROR;
+ return mBinder.insert(key, value, uid, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
- return false;
+ return SYSTEM_ERROR;
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
index e555cc00ea3b..f37cf07c678d 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java
@@ -245,4 +245,12 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
put("Signature." + algorithm + " SupportedKeyClasses",
KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
}
+
+ public static String[] getSupportedEcdsaSignatureDigests() {
+ return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
+ }
+
+ public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() {
+ return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"};
+ }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
index 0e8d03e2c54a..19375a23d3a6 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java
@@ -31,11 +31,18 @@ import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
import java.security.Key;
+import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
import java.security.ProviderException;
+import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
@@ -43,7 +50,10 @@ import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
/**
* Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers.
@@ -140,11 +150,18 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
- if ((opmode != Cipher.ENCRYPT_MODE) && (opmode != Cipher.DECRYPT_MODE)) {
- throw new UnsupportedOperationException(
- "Only ENCRYPT and DECRYPT modes supported. Mode: " + opmode);
+ switch (opmode) {
+ case Cipher.ENCRYPT_MODE:
+ case Cipher.WRAP_MODE:
+ mEncrypting = true;
+ break;
+ case Cipher.DECRYPT_MODE:
+ case Cipher.UNWRAP_MODE:
+ mEncrypting = false;
+ break;
+ default:
+ throw new InvalidParameterException("Unsupported opmode: " + opmode);
}
- mEncrypting = opmode == Cipher.ENCRYPT_MODE;
initKey(opmode, key);
if (mKey == null) {
throw new ProviderException("initKey did not initialize the key");
@@ -395,13 +412,139 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
- return super.engineWrap(key);
+ if (mKey == null) {
+ throw new IllegalStateException("Not initilized");
+ }
+
+ if (!isEncrypting()) {
+ throw new IllegalStateException(
+ "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
+ }
+
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+ byte[] encoded = null;
+ if (key instanceof SecretKey) {
+ if ("RAW".equalsIgnoreCase(key.getFormat())) {
+ encoded = key.getEncoded();
+ }
+ if (encoded == null) {
+ try {
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm());
+ SecretKeySpec spec =
+ (SecretKeySpec) keyFactory.getKeySpec(
+ (SecretKey) key, SecretKeySpec.class);
+ encoded = spec.getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material",
+ e);
+ }
+ }
+ } else if (key instanceof PrivateKey) {
+ if ("PKCS8".equalsIgnoreCase(key.getFormat())) {
+ encoded = key.getEncoded();
+ }
+ if (encoded == null) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
+ PKCS8EncodedKeySpec spec =
+ keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class);
+ encoded = spec.getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material",
+ e);
+ }
+ }
+ } else if (key instanceof PublicKey) {
+ if ("X.509".equalsIgnoreCase(key.getFormat())) {
+ encoded = key.getEncoded();
+ }
+ if (encoded == null) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
+ X509EncodedKeySpec spec =
+ keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
+ encoded = spec.getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material",
+ e);
+ }
+ }
+ } else {
+ throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName());
+ }
+
+ if (encoded == null) {
+ throw new InvalidKeyException(
+ "Failed to wrap key because it does not export its key material");
+ }
+
+ try {
+ return engineDoFinal(encoded, 0, encoded.length);
+ } catch (BadPaddingException e) {
+ throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e);
+ }
}
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
- return super.engineUnwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType);
+ if (mKey == null) {
+ throw new IllegalStateException("Not initilized");
+ }
+
+ if (isEncrypting()) {
+ throw new IllegalStateException(
+ "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys");
+ }
+
+ if (wrappedKey == null) {
+ throw new NullPointerException("wrappedKey == null");
+ }
+
+ byte[] encoded;
+ try {
+ encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ throw new InvalidKeyException("Failed to unwrap key", e);
+ }
+
+ switch (wrappedKeyType) {
+ case Cipher.SECRET_KEY:
+ {
+ return new SecretKeySpec(encoded, wrappedKeyAlgorithm);
+ // break;
+ }
+ case Cipher.PRIVATE_KEY:
+ {
+ KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
+ try {
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded));
+ } catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to create private key from its PKCS#8 encoded form", e);
+ }
+ // break;
+ }
+ case Cipher.PUBLIC_KEY:
+ {
+ KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm);
+ try {
+ return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
+ } catch (InvalidKeySpecException e) {
+ throw new InvalidKeyException(
+ "Failed to create public key from its X.509 encoded form", e);
+ }
+ // break;
+ }
+ default:
+ throw new InvalidParameterException(
+ "Unsupported wrappedKeyType: " + wrappedKeyType);
+ }
}
@Override
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 4d6178f923d1..688936c79dc5 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -179,11 +179,15 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
spec.getEncryptionPaddings());
+ if (spec.getSignaturePaddings().length > 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Signature paddings not supported for symmetric key algorithms");
+ }
mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (spec.isRandomizedEncryptionRequired())) {
for (int keymasterBlockMode : mKeymasterBlockModes) {
- if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(
+ if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
keymasterBlockMode)) {
throw new InvalidAlgorithmParameterException(
"Randomized encryption (IND-CPA) required but may be violated"
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index c5ea0f7428c5..69155a89851f 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -16,17 +16,39 @@
package android.security.keystore;
-import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.security.Credentials;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
+import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
+import com.android.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.org.bouncycastle.asn1.ASN1Integer;
+import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.org.bouncycastle.asn1.DERBitString;
+import com.android.org.bouncycastle.asn1.DERInteger;
+import com.android.org.bouncycastle.asn1.DERNull;
+import com.android.org.bouncycastle.asn1.DERSequence;
+import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import com.android.org.bouncycastle.asn1.x509.Certificate;
+import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.org.bouncycastle.asn1.x509.TBSCertificate;
+import com.android.org.bouncycastle.asn1.x509.Time;
+import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import com.android.org.bouncycastle.jce.X509Principal;
+import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
-import com.android.org.conscrypt.NativeConstants;
import com.android.org.conscrypt.OpenSSLEngine;
+import libcore.util.EmptyArray;
+
+import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
@@ -41,10 +63,19 @@ import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
/**
* Provides a way to create instances of a KeyPair which will be placed in the
@@ -63,13 +94,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi {
public RSA() {
- super(KeyProperties.KEY_ALGORITHM_RSA);
+ super(KeymasterDefs.KM_ALGORITHM_RSA);
}
}
public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi {
public EC() {
- super(KeyProperties.KEY_ALGORITHM_EC);
+ super(KeymasterDefs.KM_ALGORITHM_EC);
}
}
@@ -87,39 +118,296 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
private static final int RSA_MIN_KEY_SIZE = 512;
private static final int RSA_MAX_KEY_SIZE = 8192;
- private final String mAlgorithm;
+ private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE =
+ new HashMap<String, Integer>();
+ private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>();
+ static {
+ // Aliases for NIST P-192
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-192", 192);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp192r1", 192);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime192v1", 192);
+
+ // Aliases for NIST P-224
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224);
+
+ // Aliases for NIST P-256
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256);
+
+ // Aliases for NIST P-384
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384);
+
+ // Aliases for NIST P-521
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521);
+ SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521);
+
+ SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet());
+ Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES);
+ }
+ private final int mOriginalKeymasterAlgorithm;
private KeyStore mKeyStore;
private KeyGenParameterSpec mSpec;
+
+ private String mEntryAlias;
private boolean mEncryptionAtRestRequired;
- private @KeyProperties.KeyAlgorithmEnum String mKeyAlgorithm;
- private int mKeyType;
- private int mKeySize;
+ private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm;
+ private int mKeymasterAlgorithm = -1;
+ private int mKeySizeBits;
+ private SecureRandom mRng;
+
+ private int[] mKeymasterPurposes;
+ private int[] mKeymasterBlockModes;
+ private int[] mKeymasterEncryptionPaddings;
+ private int[] mKeymasterSignaturePaddings;
+ private int[] mKeymasterDigests;
- protected AndroidKeyStoreKeyPairGeneratorSpi(@KeyProperties.KeyAlgorithmEnum String algorithm) {
- mAlgorithm = algorithm;
+ private long mRSAPublicExponent;
+
+ protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) {
+ mOriginalKeymasterAlgorithm = keymasterAlgorithm;
}
- @KeyProperties.KeyAlgorithmEnum String getAlgorithm() {
- return mAlgorithm;
+ @Override
+ public void initialize(int keysize, SecureRandom random) {
+ throw new IllegalArgumentException(
+ KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName()
+ + " required to initialize this KeyPairGenerator");
+ }
+
+ @Override
+ public void initialize(AlgorithmParameterSpec params, SecureRandom random)
+ throws InvalidAlgorithmParameterException {
+ resetAll();
+
+ boolean success = false;
+ try {
+ if (params == null) {
+ throw new InvalidAlgorithmParameterException(
+ "Must supply params of type " + KeyGenParameterSpec.class.getName()
+ + " or " + KeyPairGeneratorSpec.class.getName());
+ }
+
+ KeyGenParameterSpec spec;
+ boolean encryptionAtRestRequired = false;
+ int keymasterAlgorithm = mOriginalKeymasterAlgorithm;
+ if (params instanceof KeyGenParameterSpec) {
+ spec = (KeyGenParameterSpec) params;
+ } else if (params instanceof KeyPairGeneratorSpec) {
+ // Legacy/deprecated spec
+ KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
+ try {
+ KeyGenParameterSpec.Builder specBuilder;
+ String specKeyAlgorithm = legacySpec.getKeyType();
+ if (specKeyAlgorithm != null) {
+ // Spec overrides the generator's default key algorithm
+ try {
+ keymasterAlgorithm =
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+ specKeyAlgorithm);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(
+ "Invalid key type in parameters", e);
+ }
+ }
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ specBuilder = new KeyGenParameterSpec.Builder(
+ legacySpec.getKeystoreAlias(),
+ KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ specBuilder.setDigests(
+ KeyProperties.DIGEST_NONE,
+ KeyProperties.DIGEST_MD5,
+ KeyProperties.DIGEST_SHA1,
+ KeyProperties.DIGEST_SHA224,
+ KeyProperties.DIGEST_SHA256,
+ KeyProperties.DIGEST_SHA384,
+ KeyProperties.DIGEST_SHA512);
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ specBuilder = new KeyGenParameterSpec.Builder(
+ legacySpec.getKeystoreAlias(),
+ KeyProperties.PURPOSE_ENCRYPT
+ | KeyProperties.PURPOSE_DECRYPT
+ | KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ specBuilder.setDigests(
+ KeyProperties.DIGEST_NONE,
+ KeyProperties.DIGEST_MD5,
+ KeyProperties.DIGEST_SHA1,
+ KeyProperties.DIGEST_SHA224,
+ KeyProperties.DIGEST_SHA256,
+ KeyProperties.DIGEST_SHA384,
+ KeyProperties.DIGEST_SHA512);
+ specBuilder.setSignaturePaddings(
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+ specBuilder.setEncryptionPaddings(
+ KeyProperties.ENCRYPTION_PADDING_NONE,
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
+ // Disable randomized encryption requirement to support encryption
+ // padding NONE above.
+ specBuilder.setRandomizedEncryptionRequired(false);
+ break;
+ default:
+ throw new ProviderException(
+ "Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+
+ if (legacySpec.getKeySize() != -1) {
+ specBuilder.setKeySize(legacySpec.getKeySize());
+ }
+ if (legacySpec.getAlgorithmParameterSpec() != null) {
+ specBuilder.setAlgorithmParameterSpec(
+ legacySpec.getAlgorithmParameterSpec());
+ }
+ specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
+ specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
+ specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
+ specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
+ encryptionAtRestRequired = legacySpec.isEncryptionRequired();
+ specBuilder.setUserAuthenticationRequired(false);
+
+ spec = specBuilder.build();
+ } catch (NullPointerException | IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+ } else {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported params class: " + params.getClass().getName()
+ + ". Supported: " + KeyGenParameterSpec.class.getName()
+ + ", " + KeyPairGeneratorSpec.class.getName());
+ }
+
+ mEntryAlias = spec.getKeystoreAlias();
+ mSpec = spec;
+ mKeymasterAlgorithm = keymasterAlgorithm;
+ mEncryptionAtRestRequired = encryptionAtRestRequired;
+ mKeySizeBits = spec.getKeySize();
+ initAlgorithmSpecificParameters();
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = getDefaultKeySize(keymasterAlgorithm);
+ }
+ checkValidKeySize(keymasterAlgorithm, mKeySizeBits);
+
+ if (spec.getKeystoreAlias() == null) {
+ throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided");
+ }
+
+ String jcaKeyAlgorithm;
+ try {
+ jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
+ keymasterAlgorithm);
+ mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes());
+ mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes());
+ mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster(
+ spec.getSignaturePaddings());
+ if (spec.isDigestsSpecified()) {
+ mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests());
+ } else {
+ mKeymasterDigests = EmptyArray.INT;
+ }
+ } catch (IllegalArgumentException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+
+ mJcaKeyAlgorithm = jcaKeyAlgorithm;
+ mRng = random;
+ mKeyStore = KeyStore.getInstance();
+ success = true;
+ } finally {
+ if (!success) {
+ resetAll();
+ }
+ }
+ }
+
+ private void resetAll() {
+ mEntryAlias = null;
+ mJcaKeyAlgorithm = null;
+ mKeymasterAlgorithm = -1;
+ mKeymasterPurposes = null;
+ mKeymasterBlockModes = null;
+ mKeymasterEncryptionPaddings = null;
+ mKeymasterSignaturePaddings = null;
+ mKeymasterDigests = null;
+ mKeySizeBits = 0;
+ mSpec = null;
+ mRSAPublicExponent = -1;
+ mEncryptionAtRestRequired = false;
+ mRng = null;
+ mKeyStore = null;
+ }
+
+ private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException {
+ AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec();
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ {
+ BigInteger publicExponent = null;
+ if (algSpecificSpec instanceof RSAKeyGenParameterSpec) {
+ RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec;
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = rsaSpec.getKeysize();
+ } else if (mKeySizeBits != rsaSpec.getKeysize()) {
+ throw new InvalidAlgorithmParameterException("RSA key size must match "
+ + " between " + mSpec + " and " + algSpecificSpec
+ + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize());
+ }
+ publicExponent = rsaSpec.getPublicExponent();
+ } else if (algSpecificSpec != null) {
+ throw new InvalidAlgorithmParameterException(
+ "RSA may only use RSAKeyGenParameterSpec");
+ }
+ if (publicExponent == null) {
+ publicExponent = RSAKeyGenParameterSpec.F4;
+ }
+ if (publicExponent.compareTo(BigInteger.ZERO) < 1) {
+ throw new InvalidAlgorithmParameterException(
+ "RSA public exponent must be positive: " + publicExponent);
+ }
+ if (publicExponent.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported RSA public exponent: " + publicExponent
+ + ". Only exponents <= " + Long.MAX_VALUE + " supported");
+ }
+ mRSAPublicExponent = publicExponent.longValue();
+ break;
+ }
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ if (algSpecificSpec instanceof ECGenParameterSpec) {
+ ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
+ String curveName = ecSpec.getName();
+ Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get(
+ curveName.toLowerCase(Locale.US));
+ if (ecSpecKeySizeBits == null) {
+ throw new InvalidAlgorithmParameterException(
+ "Unsupported EC curve name: " + curveName
+ + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES);
+ }
+ if (mKeySizeBits == -1) {
+ mKeySizeBits = ecSpecKeySizeBits;
+ } else if (mKeySizeBits != ecSpecKeySizeBits) {
+ throw new InvalidAlgorithmParameterException("EC key size must match "
+ + " between " + mSpec + " and " + algSpecificSpec
+ + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits);
+ }
+ } else if (algSpecificSpec != null) {
+ throw new InvalidAlgorithmParameterException(
+ "EC may only use ECGenParameterSpec");
+ }
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
}
- /**
- * Generate a KeyPair which is backed by the Android keystore service. You
- * must call {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
- * with an {@link KeyPairGeneratorSpec} as the {@code params}
- * argument before calling this otherwise an {@code IllegalStateException}
- * will be thrown.
- * <p>
- * This will create an entry in the Android keystore service with a
- * self-signed certificate using the {@code params} specified in the
- * {@code initialize(params)} call.
- *
- * @throws IllegalStateException when called before calling
- * {@link KeyPairGenerator#initialize(AlgorithmParameterSpec)}
- * @see java.security.KeyPairGeneratorSpi#generateKeyPair()
- */
@Override
public KeyPair generateKeyPair() {
if (mKeyStore == null || mSpec == null) {
@@ -134,18 +422,65 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
+ ", but the user has not yet entered the credential");
}
- final String alias = mSpec.getKeystoreAlias();
-
- byte[][] args = getArgsForKeyType(mKeyType, mSpec.getAlgorithmParameterSpec());
-
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
+ KeymasterArguments args = new KeymasterArguments();
+ args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
+ args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
+ args.addInts(KeymasterDefs.KM_TAG_PURPOSE, mKeymasterPurposes);
+ args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes);
+ args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterEncryptionPaddings);
+ args.addInts(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings);
+ args.addInts(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests);
+
+ // TODO: Remove the digest and padding NONE workaround below once Android Keystore returns
+ // keys which are backed by AndroidKeyStoreBCWorkaround provider instead of Conscrypt. The
+ // workaround is needed because Conscrypt (via keystore-engine) uses old KeyStore API which
+ // translates into digest NONE and padding NONE in the new API. keystore-engine cannot be
+ // updated to pass in the correct padding and digest values because it uses
+ // OpenSSL/BoringSSL engine which performs digesting and padding prior before invoking
+ // KeyStore API.
+ if (!com.android.internal.util.ArrayUtils.contains(
+ mKeymasterDigests, KeymasterDefs.KM_DIGEST_NONE)) {
+ args.addInt(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
+ }
+ if ((!com.android.internal.util.ArrayUtils.contains(
+ mKeymasterSignaturePaddings, KeymasterDefs.KM_PAD_NONE))
+ && (!com.android.internal.util.ArrayUtils.contains(
+ mKeymasterEncryptionPaddings, KeymasterDefs.KM_PAD_NONE))) {
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ }
+ KeymasterUtils.addUserAuthArgs(args,
+ mSpec.isUserAuthenticationRequired(),
+ mSpec.getUserAuthenticationValidityDurationSeconds());
+ args.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ (mSpec.getKeyValidityStart() != null)
+ ? mSpec.getKeyValidityStart() : new Date(0));
+ args.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ (mSpec.getKeyValidityForOriginationEnd() != null)
+ ? mSpec.getKeyValidityForOriginationEnd() : new Date(Long.MAX_VALUE));
+ args.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ (mSpec.getKeyValidityForConsumptionEnd() != null)
+ ? mSpec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
+ addAlgorithmSpecificParameters(args);
+
+ byte[] additionalEntropy =
+ KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+ mRng, (mKeySizeBits + 7) / 8);
+
+ final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
boolean success = false;
try {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- if (!mKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF, mKeyType, mKeySize,
- flags, args)) {
- throw new IllegalStateException("could not generate key in keystore");
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias);
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ int errorCode = mKeyStore.generateKey(
+ privateKeyAlias,
+ args,
+ additionalEntropy,
+ flags,
+ resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException(
+ "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
}
final PrivateKey privKey;
@@ -153,7 +488,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
try {
privKey = engine.getPrivateKeyById(privateKeyAlias);
} catch (InvalidKeyException e) {
- throw new RuntimeException("Can't get key", e);
+ throw new ProviderException("Failed to obtain generated private key", e);
}
ExportResult exportResult =
@@ -163,39 +498,45 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
throw new KeyStoreConnectException();
} else if (exportResult.resultCode != KeyStore.NO_ERROR) {
throw new ProviderException(
- "Failed to obtain public key in X.509 format",
+ "Failed to obtain X.509 form of generated public key",
KeyStore.getKeyStoreException(exportResult.resultCode));
}
final byte[] pubKeyBytes = exportResult.exportData;
-
final PublicKey pubKey;
try {
- final KeyFactory keyFact = KeyFactory.getInstance(mKeyAlgorithm);
+ final KeyFactory keyFact = KeyFactory.getInstance(mJcaKeyAlgorithm);
pubKey = keyFact.generatePublic(new X509EncodedKeySpec(pubKeyBytes));
} catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Can't instantiate key generator", e);
+ throw new ProviderException(
+ "Failed to obtain " + mJcaKeyAlgorithm + " KeyFactory", e);
} catch (InvalidKeySpecException e) {
- throw new IllegalStateException("keystore returned invalid key encoding", e);
+ throw new ProviderException("Invalid X.509 encoding of generated public key", e);
}
final X509Certificate cert;
try {
- cert = generateCertificate(privKey, pubKey);
+ cert = generateSelfSignedCertificate(privKey, pubKey);
} catch (Exception e) {
- throw new IllegalStateException("Can't generate certificate", e);
+ throw new ProviderException("Failed to generate self-signed certificate", e);
}
byte[] certBytes;
try {
certBytes = cert.getEncoded();
} catch (CertificateEncodingException e) {
- throw new IllegalStateException("Can't get encoding of certificate", e);
+ throw new ProviderException(
+ "Failed to obtain encoded form of self-signed certificate", e);
}
- if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, certBytes, KeyStore.UID_SELF,
- flags)) {
- throw new IllegalStateException("Can't store certificate in AndroidKeyStore");
+ int insertErrorCode = mKeyStore.insert(
+ Credentials.USER_CERTIFICATE + mEntryAlias,
+ certBytes,
+ KeyStore.UID_SELF,
+ flags);
+ if (insertErrorCode != KeyStore.NO_ERROR) {
+ throw new ProviderException("Failed to store self-signed certificate",
+ KeyStore.getKeyStoreException(insertErrorCode));
}
KeyPair result = new KeyPair(pubKey, privKey);
@@ -203,14 +544,41 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
return result;
} finally {
if (!success) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+ Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias);
}
}
}
+ private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) {
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ keymasterArgs.addLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent);
+ break;
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm);
+ }
+ }
+
+ private X509Certificate generateSelfSignedCertificate(
+ PrivateKey privateKey, PublicKey publicKey) throws Exception {
+ String signatureAlgorithm =
+ getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec);
+ if (signatureAlgorithm == null) {
+ // Key cannot be used to sign a certificate
+ return generateSelfSignedCertificateWithFakeSignature(publicKey);
+ } else {
+ // Key can be used to sign a certificate
+ return generateSelfSignedCertificateWithValidSignature(
+ privateKey, publicKey, signatureAlgorithm);
+ }
+ }
+
@SuppressWarnings("deprecation")
- private X509Certificate generateCertificate(PrivateKey privateKey, PublicKey publicKey)
- throws Exception {
+ private X509Certificate generateSelfSignedCertificateWithValidSignature(
+ PrivateKey privateKey, PublicKey publicKey, String signatureAlgorithm)
+ throws Exception {
final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
certGen.setPublicKey(publicKey);
certGen.setSerialNumber(mSpec.getCertificateSerialNumber());
@@ -218,198 +586,223 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
certGen.setIssuerDN(mSpec.getCertificateSubject());
certGen.setNotBefore(mSpec.getCertificateNotBefore());
certGen.setNotAfter(mSpec.getCertificateNotAfter());
- certGen.setSignatureAlgorithm(getDefaultSignatureAlgorithmForKeyAlgorithm(mKeyAlgorithm));
+ certGen.setSignatureAlgorithm(signatureAlgorithm);
return certGen.generate(privateKey);
}
- @NonNull
- private @KeyProperties.KeyAlgorithmEnum String getKeyAlgorithm(KeyPairGeneratorSpec spec) {
- String result = spec.getKeyType();
- if (result != null) {
- return result;
+ @SuppressWarnings("deprecation")
+ private X509Certificate generateSelfSignedCertificateWithFakeSignature(
+ PublicKey publicKey) throws Exception {
+ V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator();
+ ASN1ObjectIdentifier sigAlgOid;
+ AlgorithmIdentifier sigAlgId;
+ byte[] signature;
+ switch (mKeymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ sigAlgOid = X9ObjectIdentifiers.ecdsa_with_SHA256;
+ sigAlgId = new AlgorithmIdentifier(sigAlgOid);
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new DERInteger(0));
+ v.add(new DERInteger(0));
+ signature = new DERSequence().getEncoded();
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ sigAlgOid = PKCSObjectIdentifiers.sha256WithRSAEncryption;
+ sigAlgId = new AlgorithmIdentifier(sigAlgOid, DERNull.INSTANCE);
+ signature = new byte[1];
+ break;
+ default:
+ throw new ProviderException("Unsupported key algorithm: " + mKeymasterAlgorithm);
}
- return getAlgorithm();
- }
- private static int getDefaultKeySize(int keyType) {
- if (keyType == NativeConstants.EVP_PKEY_EC) {
- return EC_DEFAULT_KEY_SIZE;
- } else if (keyType == NativeConstants.EVP_PKEY_RSA) {
- return RSA_DEFAULT_KEY_SIZE;
+ try (ASN1InputStream publicKeyInfoIn = new ASN1InputStream(publicKey.getEncoded())) {
+ tbsGenerator.setSubjectPublicKeyInfo(
+ SubjectPublicKeyInfo.getInstance(publicKeyInfoIn.readObject()));
}
- return -1;
+ tbsGenerator.setSerialNumber(new ASN1Integer(mSpec.getCertificateSerialNumber()));
+ X509Principal subject =
+ new X509Principal(mSpec.getCertificateSubject().getEncoded());
+ tbsGenerator.setSubject(subject);
+ tbsGenerator.setIssuer(subject);
+ tbsGenerator.setStartDate(new Time(mSpec.getCertificateNotBefore()));
+ tbsGenerator.setEndDate(new Time(mSpec.getCertificateNotAfter()));
+ tbsGenerator.setSignature(sigAlgId);
+ TBSCertificate tbsCertificate = tbsGenerator.generateTBSCertificate();
+
+ ASN1EncodableVector result = new ASN1EncodableVector();
+ result.add(tbsCertificate);
+ result.add(sigAlgId);
+ result.add(new DERBitString(signature));
+ return new X509CertificateObject(Certificate.getInstance(new DERSequence(result)));
}
- private static void checkValidKeySize(String keyAlgorithm, int keyType, int keySize)
- throws InvalidAlgorithmParameterException {
- if (keyType == NativeConstants.EVP_PKEY_EC) {
- if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) {
- throw new InvalidAlgorithmParameterException("EC keys must be >= "
- + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE);
- }
- } else if (keyType == NativeConstants.EVP_PKEY_RSA) {
- if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
- throw new InvalidAlgorithmParameterException("RSA keys must be >= "
- + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE);
- }
- } else {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
+ private static int getDefaultKeySize(int keymasterAlgorithm) {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return EC_DEFAULT_KEY_SIZE;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA_DEFAULT_KEY_SIZE;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
}
}
- private static void checkCorrectParametersSpec(int keyType, int keySize,
- AlgorithmParameterSpec spec) throws InvalidAlgorithmParameterException {
- if (keyType == NativeConstants.EVP_PKEY_RSA && spec != null) {
- if (spec instanceof RSAKeyGenParameterSpec) {
- RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
- if (keySize != -1 && keySize != rsaSpec.getKeysize()) {
- throw new InvalidAlgorithmParameterException("RSA key size must match: "
- + keySize + " vs " + rsaSpec.getKeysize());
+ private static void checkValidKeySize(int keymasterAlgorithm, int keySize)
+ throws InvalidAlgorithmParameterException {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ if (keySize < EC_MIN_KEY_SIZE || keySize > EC_MAX_KEY_SIZE) {
+ throw new InvalidAlgorithmParameterException("EC key size must be >= "
+ + EC_MIN_KEY_SIZE + " and <= " + EC_MAX_KEY_SIZE);
}
- } else {
- throw new InvalidAlgorithmParameterException(
- "RSA may only use RSAKeyGenParameterSpec");
- }
- }
- }
-
- private static String getDefaultSignatureAlgorithmForKeyAlgorithm(
- @KeyProperties.KeyAlgorithmEnum String algorithm) {
- if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
- return "sha256WithRSA";
- } else if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
- return "sha256WithECDSA";
- } else {
- throw new IllegalArgumentException("Unsupported key type " + algorithm);
- }
- }
-
- private static byte[][] getArgsForKeyType(int keyType, AlgorithmParameterSpec spec) {
- switch (keyType) {
- case NativeConstants.EVP_PKEY_RSA:
- if (spec instanceof RSAKeyGenParameterSpec) {
- RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) spec;
- return new byte[][] { rsaSpec.getPublicExponent().toByteArray() };
+ break;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) {
+ throw new InvalidAlgorithmParameterException("RSA key size must be >= "
+ + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE);
}
break;
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
}
- return null;
}
- @Override
- public void initialize(int keysize, SecureRandom random) {
- throw new IllegalArgumentException(
- "cannot specify keysize with AndroidKeyStore KeyPairGenerator");
- }
-
- @Override
- public void initialize(AlgorithmParameterSpec params, SecureRandom random)
- throws InvalidAlgorithmParameterException {
- if (params == null) {
- throw new InvalidAlgorithmParameterException(
- "Must supply params of type " + KeyGenParameterSpec.class.getName()
- + " or " + KeyPairGeneratorSpec.class.getName());
+ /**
+ * Returns the {@code Signature} algorithm to be used for signing a certificate using the
+ * specified key or {@code null} if the key cannot be used for signing a certificate.
+ */
+ @Nullable
+ private static String getCertificateSignatureAlgorithm(
+ int keymasterAlgorithm,
+ int keySizeBits,
+ KeyGenParameterSpec spec) {
+ // Constraints:
+ // 1. Key must be authorized for signing.
+ // 2. Signature digest must be one of key's authorized digests.
+ // 3. For RSA keys, the digest output size must not exceed modulus size minus space needed
+ // for RSA PKCS#1 signature padding (about 29 bytes: minimum 10 bytes of padding + 15--19
+ // bytes overhead for encoding digest OID and digest value in DER).
+ // 4. For EC keys, the there is no point in using a digest whose output size is longer than
+ // key/field size because the digest will be truncated to that size.
+
+ if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) {
+ // Key not authorized for signing
+ return null;
}
-
- String keyAlgorithm;
- KeyGenParameterSpec spec;
- boolean encryptionAtRestRequired = false;
- if (params instanceof KeyPairGeneratorSpec) {
- KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params;
- try {
- KeyGenParameterSpec.Builder specBuilder;
- keyAlgorithm = getKeyAlgorithm(legacySpec).toUpperCase(Locale.US);
- if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
- specBuilder = new KeyGenParameterSpec.Builder(
- legacySpec.getKeystoreAlias(),
- KeyProperties.PURPOSE_SIGN
- | KeyProperties.PURPOSE_VERIFY);
- specBuilder.setDigests(
- KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_MD5,
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512);
- } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
- specBuilder = new KeyGenParameterSpec.Builder(
- legacySpec.getKeystoreAlias(),
- KeyProperties.PURPOSE_ENCRYPT
- | KeyProperties.PURPOSE_DECRYPT
- | KeyProperties.PURPOSE_SIGN
- | KeyProperties.PURPOSE_VERIFY);
- specBuilder.setDigests(
- KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_MD5,
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512);
- specBuilder.setSignaturePaddings(
- KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB);
- specBuilder.setEncryptionPaddings(
- KeyProperties.ENCRYPTION_PADDING_NONE,
- KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
- // Disable randomized encryption requirement to support encryption padding NONE
- // above.
- specBuilder.setRandomizedEncryptionRequired(false);
- } else {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
+ if (!spec.isDigestsSpecified()) {
+ // Key not authorized for any digests -- can't sign
+ return null;
+ }
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ {
+ Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
+ spec.getDigests(),
+ AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
+
+ int bestKeymasterDigest = -1;
+ int bestDigestOutputSizeBits = -1;
+ for (int keymasterDigest : availableKeymasterDigests) {
+ int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ if (outputSizeBits == keySizeBits) {
+ // Perfect match -- use this digest
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ break;
+ }
+ // Not a perfect match -- check against the best digest so far
+ if (bestKeymasterDigest == -1) {
+ // First digest tested -- definitely the best so far
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ } else {
+ // Prefer output size to be as close to key size as possible, with output
+ // sizes larger than key size preferred to those smaller than key size.
+ if (bestDigestOutputSizeBits < keySizeBits) {
+ // Output size of the best digest so far is smaller than key size.
+ // Anything larger is a win.
+ if (outputSizeBits > bestDigestOutputSizeBits) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ } else {
+ // Output size of the best digest so far is larger than key size.
+ // Anything smaller is a win, as long as it's not smaller than key size.
+ if ((outputSizeBits < bestDigestOutputSizeBits)
+ && (outputSizeBits >= keySizeBits)) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ }
+ }
}
-
- if (legacySpec.getKeySize() != -1) {
- specBuilder.setKeySize(legacySpec.getKeySize());
+ if (bestKeymasterDigest == -1) {
+ return null;
+ }
+ return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
+ bestKeymasterDigest) + "WithECDSA";
+ }
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ {
+ Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests(
+ spec.getDigests(),
+ AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests());
+
+ // The amount of space available for the digest is less than modulus size because
+ // padding must be at least 10 bytes long, and then there's also the 15--19
+ // bytes overhead for encoding digest OID and digest value in DER.
+ int maxDigestOutputSizeBits = keySizeBits - 29 * 8;
+ int bestKeymasterDigest = -1;
+ int bestDigestOutputSizeBits = -1;
+ for (int keymasterDigest : availableKeymasterDigests) {
+ int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest);
+ if (outputSizeBits > maxDigestOutputSizeBits) {
+ // Digest too long (signature generation will fail) -- skip
+ continue;
+ }
+ if (bestKeymasterDigest == -1) {
+ // First digest tested -- definitely the best so far
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ } else {
+ // The longer the better
+ if (outputSizeBits > bestDigestOutputSizeBits) {
+ bestKeymasterDigest = keymasterDigest;
+ bestDigestOutputSizeBits = outputSizeBits;
+ }
+ }
}
- if (legacySpec.getAlgorithmParameterSpec() != null) {
- specBuilder.setAlgorithmParameterSpec(legacySpec.getAlgorithmParameterSpec());
+ if (bestKeymasterDigest == -1) {
+ return null;
}
- specBuilder.setCertificateSubject(legacySpec.getSubjectDN());
- specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber());
- specBuilder.setCertificateNotBefore(legacySpec.getStartDate());
- specBuilder.setCertificateNotAfter(legacySpec.getEndDate());
- encryptionAtRestRequired = legacySpec.isEncryptionRequired();
- specBuilder.setUserAuthenticationRequired(false);
-
- spec = specBuilder.build();
- } catch (NullPointerException | IllegalArgumentException e) {
- throw new InvalidAlgorithmParameterException(e);
+ return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest(
+ bestKeymasterDigest) + "WithRSA";
}
- } else if (params instanceof KeyGenParameterSpec) {
- spec = (KeyGenParameterSpec) params;
- keyAlgorithm = getAlgorithm();
- } else {
- throw new InvalidAlgorithmParameterException(
- "Unsupported params class: " + params.getClass().getName()
- + ". Supported: " + KeyGenParameterSpec.class.getName()
- + ", " + KeyPairGeneratorSpec.class);
+ default:
+ throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm);
}
+ }
- int keyType = KeyStore.getKeyTypeForAlgorithm(keyAlgorithm);
- if (keyType == -1) {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
+ private static Set<Integer> getAvailableKeymasterSignatureDigests(
+ @KeyProperties.DigestEnum String[] authorizedKeyDigests,
+ @KeyProperties.DigestEnum String[] supportedSignatureDigests) {
+ Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>();
+ for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) {
+ authorizedKeymasterKeyDigests.add(keymasterDigest);
}
- int keySize = spec.getKeySize();
- if (keySize == -1) {
- keySize = getDefaultKeySize(keyType);
- if (keySize == -1) {
- throw new InvalidAlgorithmParameterException(
- "Unsupported key algorithm: " + keyAlgorithm);
- }
+ Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>();
+ for (int keymasterDigest
+ : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) {
+ supportedKeymasterSignatureDigests.add(keymasterDigest);
+ }
+ if (authorizedKeymasterKeyDigests.contains(KeymasterDefs.KM_DIGEST_NONE)) {
+ // Key is authorized to be used with any digest
+ return supportedKeymasterSignatureDigests;
+ } else {
+ // Key is authorized to be used only with specific digests.
+ Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests);
+ result.retainAll(authorizedKeymasterKeyDigests);
+ return result;
}
- checkCorrectParametersSpec(keyType, keySize, spec.getAlgorithmParameterSpec());
- checkValidKeySize(keyAlgorithm, keyType, keySize);
-
- mKeyAlgorithm = keyAlgorithm;
- mKeyType = keyType;
- mKeySize = keySize;
- mSpec = spec;
- mEncryptionAtRestRequired = encryptionAtRestRequired;
- mKeyStore = KeyStore.getInstance();
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
index 5643caf05cca..d33692a2b1da 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreRSACipherSpi.java
@@ -453,7 +453,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
// Permitted
- return;
+ break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
throw new InvalidKeyException("RSA public keys cannot be used with opmode: "
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index 7c9c0cfe0169..c03be63d976c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -22,6 +22,7 @@ import com.android.org.conscrypt.OpenSSLKeyHolder;
import libcore.util.EmptyArray;
import android.security.Credentials;
+import android.security.KeyStore;
import android.security.KeyStoreParameter;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
@@ -39,7 +40,6 @@ import java.security.Key;
import java.security.KeyStore.Entry;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.ProtectionParameter;
-import java.security.KeyStore;
import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
@@ -86,7 +86,7 @@ import javax.crypto.SecretKey;
public class AndroidKeyStoreSpi extends KeyStoreSpi {
public static final String NAME = "AndroidKeyStore";
- private android.security.KeyStore mKeyStore;
+ private KeyStore mKeyStore;
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
@@ -105,8 +105,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
String keyAliasInKeystore = Credentials.USER_SECRET_KEY + alias;
int errorCode = mKeyStore.getKeyCharacteristics(
keyAliasInKeystore, null, null, keyCharacteristics);
- if ((errorCode != KeymasterDefs.KM_ERROR_OK)
- && (errorCode != android.security.KeyStore.NO_ERROR)) {
+ if (errorCode != KeyStore.NO_ERROR) {
throw (UnrecoverableKeyException)
new UnrecoverableKeyException("Failed to load information about key")
.initCause(mKeyStore.getInvalidKeyException(alias, errorCode));
@@ -272,107 +271,72 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
}
+ private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key)
+ throws KeyStoreException {
+ String keyAlgorithm = key.getAlgorithm();
+ KeyProtection.Builder specBuilder;
+ if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+ specBuilder =
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
+ specBuilder.setDigests(
+ KeyProperties.DIGEST_NONE,
+ KeyProperties.DIGEST_MD5,
+ KeyProperties.DIGEST_SHA1,
+ KeyProperties.DIGEST_SHA224,
+ KeyProperties.DIGEST_SHA256,
+ KeyProperties.DIGEST_SHA384,
+ KeyProperties.DIGEST_SHA512);
+ } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+ specBuilder =
+ new KeyProtection.Builder(
+ KeyProperties.PURPOSE_ENCRYPT
+ | KeyProperties.PURPOSE_DECRYPT
+ | KeyProperties.PURPOSE_SIGN
+ | KeyProperties.PURPOSE_VERIFY);
+ specBuilder.setDigests(
+ KeyProperties.DIGEST_NONE,
+ KeyProperties.DIGEST_MD5,
+ KeyProperties.DIGEST_SHA1,
+ KeyProperties.DIGEST_SHA224,
+ KeyProperties.DIGEST_SHA256,
+ KeyProperties.DIGEST_SHA384,
+ KeyProperties.DIGEST_SHA512);
+ specBuilder.setSignaturePaddings(
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
+ specBuilder.setEncryptionPaddings(
+ KeyProperties.ENCRYPTION_PADDING_NONE,
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
+ // Disable randomized encryption requirement to support encryption padding NONE
+ // above.
+ specBuilder.setRandomizedEncryptionRequired(false);
+ } else {
+ throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
+ }
+ specBuilder.setUserAuthenticationRequired(false);
+
+ return specBuilder.build();
+ }
+
private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
java.security.KeyStore.ProtectionParameter param) throws KeyStoreException {
int flags = 0;
KeyProtection spec;
- if (param instanceof KeyStoreParameter) {
+ if (param == null) {
+ spec = getLegacyKeyProtectionParameter(key);
+ } else if (param instanceof KeyStoreParameter) {
+ spec = getLegacyKeyProtectionParameter(key);
KeyStoreParameter legacySpec = (KeyStoreParameter) param;
- try {
- String keyAlgorithm = key.getAlgorithm();
- KeyProtection.Builder specBuilder;
- if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
- specBuilder =
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
- specBuilder.setDigests(
- KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_MD5,
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512);
- } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
- specBuilder =
- new KeyProtection.Builder(
- KeyProperties.PURPOSE_ENCRYPT
- | KeyProperties.PURPOSE_DECRYPT
- | KeyProperties.PURPOSE_SIGN
- | KeyProperties.PURPOSE_VERIFY);
- specBuilder.setDigests(
- KeyProperties.DIGEST_NONE,
- KeyProperties.DIGEST_MD5,
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA224,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA384,
- KeyProperties.DIGEST_SHA512);
- specBuilder.setSignaturePaddings(
- KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
- specBuilder.setBlockModes(KeyProperties.BLOCK_MODE_ECB);
- specBuilder.setEncryptionPaddings(
- KeyProperties.ENCRYPTION_PADDING_NONE,
- KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);
- // Disable randomized encryption requirement to support encryption padding NONE
- // above.
- specBuilder.setRandomizedEncryptionRequired(false);
- } else {
- throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm);
- }
- if (legacySpec.isEncryptionRequired()) {
- flags = android.security.KeyStore.FLAG_ENCRYPTED;
- }
- specBuilder.setUserAuthenticationRequired(false);
-
- spec = specBuilder.build();
- } catch (NullPointerException | IllegalArgumentException e) {
- throw new KeyStoreException("Unsupported protection parameter", e);
+ if (legacySpec.isEncryptionRequired()) {
+ flags = KeyStore.FLAG_ENCRYPTED;
}
} else if (param instanceof KeyProtection) {
spec = (KeyProtection) param;
- } else if (param != null) {
+ } else {
throw new KeyStoreException(
"Unsupported protection parameter class:" + param.getClass().getName()
- + ". Supported: " + KeyStoreParameter.class.getName() + ", "
- + KeyProtection.class.getName());
- } else {
- spec = null;
- }
-
- byte[] keyBytes = null;
-
- final String pkeyAlias;
- if (key instanceof OpenSSLKeyHolder) {
- pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
- } else {
- pkeyAlias = null;
- }
-
- final boolean shouldReplacePrivateKey;
- if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
- final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
- if (!alias.equals(keySubalias)) {
- throw new KeyStoreException("Can only replace keys with same alias: " + alias
- + " != " + keySubalias);
- }
-
- shouldReplacePrivateKey = false;
- } else {
- // Make sure the PrivateKey format is the one we support.
- final String keyFormat = key.getFormat();
- if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
- throw new KeyStoreException(
- "Only PrivateKeys that can be encoded into PKCS#8 are supported");
- }
-
- // Make sure we can actually encode the key.
- keyBytes = key.getEncoded();
- if (keyBytes == null) {
- throw new KeyStoreException("PrivateKey has no encoding");
- }
-
- shouldReplacePrivateKey = true;
+ + ". Supported: " + KeyProtection.class.getName() + ", "
+ + KeyStoreParameter.class.getName());
}
// Make sure the chain exists since this is a PrivateKey
@@ -400,7 +364,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
try {
userCertBytes = x509chain[0].getEncoded();
} catch (CertificateEncodingException e) {
- throw new KeyStoreException("Couldn't encode certificate #1", e);
+ throw new KeyStoreException("Failed to encode certificate #0", e);
}
/*
@@ -421,7 +385,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
certsBytes[i] = x509chain[i + 1].getEncoded();
totalCertLength += certsBytes[i].length;
} catch (CertificateEncodingException e) {
- throw new KeyStoreException("Can't encode Certificate #" + i, e);
+ throw new KeyStoreException("Failed to encode certificate #" + i, e);
}
}
@@ -441,31 +405,150 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
chainBytes = null;
}
- /*
- * Make sure we clear out all the appropriate types before trying to
- * write.
- */
- if (shouldReplacePrivateKey) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+ final String pkeyAlias;
+ if (key instanceof OpenSSLKeyHolder) {
+ pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
+ } else if (key instanceof AndroidKeyStorePrivateKey) {
+ pkeyAlias = ((AndroidKeyStoreKey) key).getAlias();
} else {
- Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
- Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
- }
-
- if (shouldReplacePrivateKey
- && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes,
- android.security.KeyStore.UID_SELF, flags)) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new KeyStoreException("Couldn't put private key in keystore");
- } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes,
- android.security.KeyStore.UID_SELF, flags)) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new KeyStoreException("Couldn't put certificate #1 in keystore");
- } else if (chainBytes != null
- && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes,
- android.security.KeyStore.UID_SELF, flags)) {
- Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- throw new KeyStoreException("Couldn't put certificate chain in keystore");
+ pkeyAlias = null;
+ }
+
+ byte[] pkcs8EncodedPrivateKeyBytes;
+ KeymasterArguments importArgs;
+ final boolean shouldReplacePrivateKey;
+ if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
+ final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
+ if (!alias.equals(keySubalias)) {
+ throw new KeyStoreException("Can only replace keys with same alias: " + alias
+ + " != " + keySubalias);
+ }
+ shouldReplacePrivateKey = false;
+ importArgs = null;
+ pkcs8EncodedPrivateKeyBytes = null;
+ } else {
+ shouldReplacePrivateKey = true;
+ // Make sure the PrivateKey format is the one we support.
+ final String keyFormat = key.getFormat();
+ if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
+ throw new KeyStoreException(
+ "Unsupported private key export format: " + keyFormat
+ + ". Only private keys which export their key material in PKCS#8 format are"
+ + " supported.");
+ }
+
+ // Make sure we can actually encode the key.
+ pkcs8EncodedPrivateKeyBytes = key.getEncoded();
+ if (pkcs8EncodedPrivateKeyBytes == null) {
+ throw new KeyStoreException("Private key did not export any key material");
+ }
+
+ importArgs = new KeymasterArguments();
+ try {
+ importArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM,
+ KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm(
+ key.getAlgorithm()));
+ @KeyProperties.PurposeEnum int purposes = spec.getPurposes();
+ importArgs.addInts(KeymasterDefs.KM_TAG_PURPOSE,
+ KeyProperties.Purpose.allToKeymaster(purposes));
+ if (spec.isDigestsSpecified()) {
+ importArgs.addInts(KeymasterDefs.KM_TAG_DIGEST,
+ KeyProperties.Digest.allToKeymaster(spec.getDigests()));
+ }
+
+ importArgs.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE,
+ KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()));
+ int[] keymasterEncryptionPaddings =
+ KeyProperties.EncryptionPadding.allToKeymaster(
+ spec.getEncryptionPaddings());
+ if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ for (int keymasterPadding : keymasterEncryptionPaddings) {
+ if (!KeymasterUtils
+ .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
+ keymasterPadding)) {
+ throw new KeyStoreException(
+ "Randomized encryption (IND-CPA) required but is violated by"
+ + " encryption padding mode: "
+ + KeyProperties.EncryptionPadding.fromKeymaster(
+ keymasterPadding)
+ + ". See KeyProtection documentation.");
+ }
+ }
+ }
+ importArgs.addInts(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings);
+ importArgs.addInts(KeymasterDefs.KM_TAG_PADDING,
+ KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings()));
+ KeymasterUtils.addUserAuthArgs(importArgs,
+ spec.isUserAuthenticationRequired(),
+ spec.getUserAuthenticationValidityDurationSeconds());
+ importArgs.addDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
+ (spec.getKeyValidityStart() != null)
+ ? spec.getKeyValidityStart() : new Date(0));
+ importArgs.addDate(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
+ (spec.getKeyValidityForOriginationEnd() != null)
+ ? spec.getKeyValidityForOriginationEnd()
+ : new Date(Long.MAX_VALUE));
+ importArgs.addDate(KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME,
+ (spec.getKeyValidityForConsumptionEnd() != null)
+ ? spec.getKeyValidityForConsumptionEnd()
+ : new Date(Long.MAX_VALUE));
+ } catch (IllegalArgumentException e) {
+ throw new KeyStoreException("Invalid parameter", e);
+ }
+ }
+
+
+ boolean success = false;
+ try {
+ // Store the private key, if necessary
+ if (shouldReplacePrivateKey) {
+ // Delete the stored private key and any related entries before importing the
+ // provided key
+ Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+ KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+ int errorCode = mKeyStore.importKey(
+ Credentials.USER_PRIVATE_KEY + alias,
+ importArgs,
+ KeymasterDefs.KM_KEY_FORMAT_PKCS8,
+ pkcs8EncodedPrivateKeyBytes,
+ flags,
+ resultingKeyCharacteristics);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to store private key",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+ } else {
+ // Keep the stored private key around -- delete all other entry types
+ Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
+ Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
+ }
+
+ // Store the leaf certificate
+ int errorCode = mKeyStore.insert(Credentials.USER_CERTIFICATE + alias, userCertBytes,
+ KeyStore.UID_SELF, flags);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to store certificate #0",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+
+ // Store the certificate chain
+ errorCode = mKeyStore.insert(Credentials.CA_CERTIFICATE + alias, chainBytes,
+ KeyStore.UID_SELF, flags);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new KeyStoreException("Failed to store certificate chain",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+ success = true;
+ } finally {
+ if (!success) {
+ if (shouldReplacePrivateKey) {
+ Credentials.deleteAllTypesForAlias(mKeyStore, alias);
+ } else {
+ Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
+ Credentials.deleteSecretKeyTypeForAlias(mKeyStore, alias);
+ }
+ }
}
}
@@ -589,7 +672,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
if (((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)
&& (params.isRandomizedEncryptionRequired())) {
for (int keymasterBlockMode : keymasterBlockModes) {
- if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatible(keymasterBlockMode)) {
+ if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ keymasterBlockMode)) {
throw new KeyStoreException(
"Randomized encryption (IND-CPA) required but may be violated by block"
+ " mode: "
@@ -598,9 +682,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
}
}
- for (int keymasterPurpose : KeyProperties.Purpose.allToKeymaster(purposes)) {
- args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
- }
+ args.addInts(KeymasterDefs.KM_TAG_PURPOSE, KeyProperties.Purpose.allToKeymaster(purposes));
args.addInts(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockModes);
if (params.getSignaturePaddings().length > 0) {
throw new KeyStoreException("Signature paddings not supported for symmetric keys");
@@ -636,7 +718,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
keyMaterial,
0, // flags
new KeyCharacteristics());
- if (errorCode != android.security.KeyStore.NO_ERROR) {
+ if (errorCode != KeyStore.NO_ERROR) {
throw new KeyStoreException("Failed to import secret key. Keystore error code: "
+ errorCode);
}
@@ -667,7 +749,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
- android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) {
+ KeyStore.UID_SELF, KeyStore.FLAG_NONE)) {
throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
}
}
@@ -840,7 +922,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
// Unfortunate name collision.
- mKeyStore = android.security.KeyStore.getInstance();
+ mKeyStore = KeyStore.getInstance();
}
@Override
@@ -852,8 +934,9 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
Credentials.deleteAllTypesForAlias(mKeyStore, alias);
- if (entry instanceof KeyStore.TrustedCertificateEntry) {
- KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
+ if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) {
+ java.security.KeyStore.TrustedCertificateEntry trE =
+ (java.security.KeyStore.TrustedCertificateEntry) entry;
engineSetCertificateEntry(alias, trE.getTrustedCertificate());
return;
}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index d8613027dc2f..19ff9c764f6e 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -214,7 +214,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
}
/**
- * Returns the requested key size or {@code -1} if default size should be used.
+ * Returns the requested key size. If {@code -1}, the size should be looked up from
+ * {@link #getAlgorithmParameterSpec()}, if provided, otherwise an algorithm-specific default
+ * size should be used.
*/
public int getKeySize() {
return mKeySize;
@@ -465,7 +467,10 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec {
* the modulus size, for EC keys this selects a curve with a matching field size, and for
* symmetric keys this sets the size of the bitstring which is their key material.
*
- * <p>The default key size is specific to each key algorithm.
+ * <p>The default key size is specific to each key algorithm. If key size is not set
+ * via this method, it should be looked up from the algorithm-specific parameters (if any)
+ * provided via
+ * {@link #setAlgorithmParameterSpec(AlgorithmParameterSpec) setAlgorithmParameterSpec}.
*/
@NonNull
public Builder setKeySize(int keySize) {
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index e3c2d1deaa3d..5af418125aee 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -168,6 +168,31 @@ public abstract class KeyProperties {
public static abstract class KeyAlgorithm {
private KeyAlgorithm() {}
+ public static int toKeymasterAsymmetricKeyAlgorithm(
+ @NonNull @KeyAlgorithmEnum String algorithm) {
+ if (KEY_ALGORITHM_EC.equalsIgnoreCase(algorithm)) {
+ return KeymasterDefs.KM_ALGORITHM_EC;
+ } else if (KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) {
+ return KeymasterDefs.KM_ALGORITHM_RSA;
+ } else {
+ throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
+ }
+ }
+
+ @NonNull
+ public static @KeyAlgorithmEnum String fromKeymasterAsymmetricKeyAlgorithm(
+ int keymasterAlgorithm) {
+ switch (keymasterAlgorithm) {
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return KEY_ALGORITHM_EC;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return KEY_ALGORITHM_RSA;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported key algorithm: " + keymasterAlgorithm);
+ }
+ }
+
public static int toKeymasterSecretKeyAlgorithm(
@NonNull @KeyAlgorithmEnum String algorithm) {
if (KEY_ALGORITHM_AES.equalsIgnoreCase(algorithm)) {
@@ -572,6 +597,28 @@ public abstract class KeyProperties {
}
@NonNull
+ public static @DigestEnum String fromKeymasterToSignatureAlgorithmDigest(int digest) {
+ switch (digest) {
+ case KeymasterDefs.KM_DIGEST_NONE:
+ return "NONE";
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return "MD5";
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return "SHA1";
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return "SHA224";
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return "SHA256";
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return "SHA384";
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return "SHA512";
+ default:
+ throw new IllegalArgumentException("Unsupported digest algorithm: " + digest);
+ }
+ }
+
+ @NonNull
public static @DigestEnum String[] allFromKeymaster(@NonNull Collection<Integer> digests) {
if (digests.isEmpty()) {
return EmptyArray.STRING;
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index e7529e12e67d..0639d49ba22a 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -50,7 +50,8 @@ public abstract class KeymasterUtils {
}
}
- public static boolean isKeymasterBlockModeIndCpaCompatible(int keymasterBlockMode) {
+ public static boolean isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto(
+ int keymasterBlockMode) {
switch (keymasterBlockMode) {
case KeymasterDefs.KM_MODE_ECB:
return false;
@@ -63,6 +64,20 @@ public abstract class KeymasterUtils {
}
}
+ public static boolean isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto(
+ int keymasterPadding) {
+ switch (keymasterPadding) {
+ case KeymasterDefs.KM_PAD_NONE:
+ return false;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return true;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported encryption padding scheme: " + keymasterPadding);
+ }
+ }
+
/**
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index cbb6fd5ef286..900a6214a22b 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -352,6 +352,7 @@ void DisplayListCanvas::drawRoundRect(
mDisplayListData->ref(rx);
mDisplayListData->ref(ry);
mDisplayListData->ref(paint);
+ refBitmapsInShader(paint->value.getShader());
addDrawOp(new (alloc()) DrawRoundRectPropsOp(&left->value, &top->value,
&right->value, &bottom->value, &rx->value, &ry->value, &paint->value));
}
@@ -366,6 +367,7 @@ void DisplayListCanvas::drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPri
mDisplayListData->ref(y);
mDisplayListData->ref(radius);
mDisplayListData->ref(paint);
+ refBitmapsInShader(paint->value.getShader());
addDrawOp(new (alloc()) DrawCirclePropsOp(&x->value, &y->value,
&radius->value, &paint->value));
}
@@ -565,5 +567,24 @@ size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
return opIndex;
}
+void DisplayListCanvas::refBitmapsInShader(const SkShader* shader) {
+ if (!shader) return;
+
+ // If this paint has an SkShader that has an SkBitmap add
+ // it to the bitmap pile
+ SkBitmap bitmap;
+ SkShader::TileMode xy[2];
+ if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) {
+ refBitmap(bitmap);
+ return;
+ }
+ SkShader::ComposeRec rec;
+ if (shader->asACompose(&rec)) {
+ refBitmapsInShader(rec.fShaderA);
+ refBitmapsInShader(rec.fShaderB);
+ return;
+ }
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index d997ef464863..edfda620ffae 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -263,6 +263,7 @@ private:
size_t addDrawOp(DrawOp* op);
size_t addRenderNodeOp(DrawRenderNodeOp* op);
+ void refBitmapsInShader(const SkShader* shader);
template<class T>
inline const T* refBuffer(const T* srcBuffer, int32_t count) {
@@ -311,6 +312,7 @@ private:
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(key, cachedPaint);
+ refBitmapsInShader(cachedPaint->getShader());
}
return cachedPaint;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 29fbf0c985d7..5850dc671889 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -524,22 +524,6 @@ protected:
inline float getLayerAlpha(const Layer* layer) const;
/**
- * Safely retrieves the ColorFilter from the given Paint. If the paint is
- * null then null is returned.
- */
- static inline SkColorFilter* getColorFilter(const SkPaint* paint) {
- return paint ? paint->getColorFilter() : nullptr;
- }
-
- /**
- * Safely retrieves the Shader from the given Paint. If the paint is
- * null then null is returned.
- */
- static inline const SkShader* getShader(const SkPaint* paint) {
- return paint ? paint->getShader() : nullptr;
- }
-
- /**
* Set to true to suppress error checks at the end of a frame.
*/
virtual bool suppressErrorChecks() const {
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index eec4960701d2..a79dd04b03dd 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -1455,15 +1455,6 @@ final public class MediaCodec {
@Retention(RetentionPolicy.SOURCE)
public @interface BufferFlag {}
- private static class FrameRenderedInfo {
- public long mPresentationTimeUs;
- public long mNanoTime;
- public FrameRenderedInfo(long presentationTimeUs, long nanoTime) {
- mPresentationTimeUs = presentationTimeUs;
- mNanoTime = nanoTime;
- }
- }
-
private EventHandler mEventHandler;
private EventHandler mOnFrameRenderedHandler;
private EventHandler mCallbackHandler;
@@ -1503,10 +1494,16 @@ final public class MediaCodec {
}
case EVENT_FRAME_RENDERED:
synchronized (mListenerLock) {
- FrameRenderedInfo info = (FrameRenderedInfo)msg.obj;
- if (mOnFrameRenderedListener != null) {
+ Map<String, Object> map = (Map<String, Object>)msg.obj;
+ for (int i = 0; ; ++i) {
+ Object mediaTimeUs = map.get(i + "-media-time-us");
+ Object systemNano = map.get(i + "-system-nano");
+ if (mediaTimeUs == null || systemNano == null
+ || mOnFrameRenderedListener == null) {
+ break;
+ }
mOnFrameRenderedListener.onFrameRendered(
- mCodec, info.mPresentationTimeUs, info.mNanoTime);
+ mCodec, (long)mediaTimeUs, (long)systemNano);
}
break;
}
@@ -2362,26 +2359,9 @@ final public class MediaCodec {
info = mDequeuedOutputInfos.remove(index);
}
}
- // TODO
- // until codec and libgui supports callback, assume frame is rendered within 50 ms
- postRenderedCallback(render, info, 50 /* delayMs */);
releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
}
- private void postRenderedCallback(boolean render, @Nullable BufferInfo info, long delayMs) {
- if (render && info != null) {
- synchronized (mListenerLock) {
- if (mOnFrameRenderedListener != null) {
- FrameRenderedInfo obj = new FrameRenderedInfo(
- info.presentationTimeUs, System.nanoTime() + delayMs * 1000000);
- Message msg = mOnFrameRenderedHandler.obtainMessage(
- EVENT_FRAME_RENDERED, obj);
- mOnFrameRenderedHandler.sendMessageDelayed(msg, delayMs);
- }
- }
- }
- }
-
/**
* If you are done with a buffer, use this call to update its surface timestamp
* and return it to the codec to render it on the output surface. If you
@@ -2440,12 +2420,6 @@ final public class MediaCodec {
info = mDequeuedOutputInfos.remove(index);
}
}
- // TODO
- // until codec and libgui supports callback, assume frame is rendered at the
- // render time or 16 ms from now, whichever is later.
- postRenderedCallback(
- true /* render */, info,
- Math.max(renderTimestampNs - System.nanoTime(), 16666666) / 1000000);
releaseOutputBuffer(
index, true /* render */, true /* updatePTS */, renderTimestampNs);
}
@@ -3049,9 +3023,12 @@ final public class MediaCodec {
} else if (mOnFrameRenderedHandler != null) {
mOnFrameRenderedHandler.removeMessages(EVENT_FRAME_RENDERED);
}
+ native_enableOnFrameRenderedListener(listener != null);
}
}
+ private native void native_enableOnFrameRenderedListener(boolean enable);
+
private EventHandler getEventHandlerOn(
@Nullable Handler handler, @NonNull EventHandler lastHandler) {
if (handler == null) {
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 8b2bd1666a06..673c4ba9bfeb 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -10,27 +10,28 @@
<p>The Android MIDI package allows users to:</p>
<ul>
- <li> Connect a MIDI keyboard to Android to play a synthesizer or drive music apps.
- <li> Connect alternative MIDI controllers to Android.
- <li> Drive external MIDI synths from Android.
- <li> Drive external peripherals, lights, show control, etc from Android.
- <li> Generate music dynamically from games or music creation apps.
- <li> Generate MIDI messages in one app and send them to a second app.
- <li> Use an Android device running in <em>peripheral mode</em> as a multitouch controller connected to a laptop.
+ <li> Connect a MIDI keyboard to Android to play a synthesizer or drive music apps.</li>
+ <li> Connect alternative MIDI controllers to Android.</li>
+ <li> Drive external MIDI synths from Android.</li>
+ <li> Drive external peripherals, lights, show control, etc from Android.</li>
+ <li> Generate music dynamically from games or music creation apps.</li>
+ <li> Generate MIDI messages in one app and send them to a second app.</li>
+ <li> Use an Android device running in <em>peripheral mode</em> as a multitouch controller
+ connected to a laptop.</li>
</ul>
<h2 id=the_api_features_include>The API features include:</h2>
-
<ul>
<li> Enumeration of currently available devices. Information includes name, vendor,
-capabilities, etc.
- <li> Provide notification when MIDI devices are plugged in or unplugged.
- <li> Support efficient transmission of single or multiple short 1-3 byte MIDI
-messages.
- <li> Support transmission of arbitrary length data for SysEx, etc.
- <li> Timestamps to avoid jitter.
- <li> Support direction connection or &ldquo;patching&rdquo; of devices for lower latency.
+capabilities, etc.</li>
+ <li> Provide notification when MIDI devices are plugged in or unplugged.</li>
+ <li> Support efficient transmission of single or multiple short 1-3 byte MIDI messages.</li>
+ <li> Support transmission of arbitrary length data for SysEx, etc.</li>
+ <li> Timestamps to avoid jitter.</li>
+ <li> Support creation of <em>virtual MIDI devices</em> that can be connected to other devices.
+ An example might be a synthesizer app that can be controlled by a composing app.</li>
+ <li> Support direction connection or &ldquo;patching&rdquo; of devices for lower latency.</li>
</ul>
<h2 id=transports_supported>Transports Supported</h2>
@@ -65,6 +66,14 @@ the MidiService.</p>
<h1 id=writing_a_midi_application>Writing a MIDI Application</h1>
+<h2 id=manifest_feature>Declare Feature in Manifest</h2>
+
+<p>An app that requires the MIDI API should declare that in the AndroidManifest.xml file.
+Then the app will not appear in the Play Store for old devices that do not support the MIDI API.</p>
+
+<pre class=prettyprint>
+&lt;uses-feature android:name="android.software.midi" android:required="true"/>
+</pre>
<h2 id=the_midimanager>The MidiManager</h2>
@@ -132,11 +141,15 @@ String manufacturer = properties
<p>Other properties include PROPERTY_PRODUCT, PROPERTY_NAME,
PROPERTY_SERIAL_NUMBER</p>
-<p>You can get the names of the ports from a PortInfo object.</p>
+<p>You can get the names and types of the ports from a PortInfo object.
+The type will be either TYPE_INPUT or TYPE_OUTPUT.</p>
<pre class=prettyprint>
-PortInfo portInfo = info.getInputPortInfo(i);
-String portName = portInfo.getName();
+MidiDeviceInfo.PortInfo[] portInfos = info.getPorts();
+String portName = portInfos[0].getName();
+if (portInfos[0].getType() == MidiDeviceInfo.PortInfo.TYPE_INPUT) {
+ ...
+}
</pre>
@@ -196,8 +209,9 @@ consistent with the other audio and input timers.</p>
<p>Here we send a message with a timestamp 2 seconds in the future.</p>
<pre class=prettyprint>
+final long NANOS_PER_SECOND = 1000000000L;
long now = System.nanoTime();
-long future = now + (2 * 1000000000);
+long future = now + (2 * NANOS_PER_SECOND);
inputPort.send(buffer, offset, numBytes, future);
</pre>
@@ -263,7 +277,8 @@ AndroidManifest.xml file.</p>
<p>The details of the resource in this example is stored in
-&ldquo;res/xml/synth_device_info.xml&rdquo;.</p>
+&ldquo;res/xml/synth_device_info.xml&rdquo;. The port names that you
+declare in this file will be available from PortInfo.getName().</p>
<pre class=prettyprint>
&lt;devices>
@@ -288,6 +303,8 @@ import android.media.midi.MidiReceiver;
public class MidiSynthDeviceService extends MidiDeviceService {
private static final String TAG = "MidiSynthDeviceService";
private MySynthEngine mSynthEngine = new MySynthEngine();
+ private boolean synthStarted = false;
+
&#64;Override
public void onCreate() {
super.onCreate();
@@ -311,10 +328,12 @@ public class MidiSynthDeviceService extends MidiDeviceService {
*/
&#64;Override
public void onDeviceStatusChanged(MidiDeviceStatus status) {
- if (status.isInputPortOpen(0)) {
+ if (status.isInputPortOpen(0) && !synthStarted) {
mSynthEngine.start();
- } else {
+ synthStarted = true;
+ } else if (!status.isInputPortOpen(0) && synthStarted){
mSynthEngine.stop();
+ synthStarted = false;
}
}
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 93b8ec7f529a..ce7f7e59bdc0 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -56,6 +56,7 @@ enum {
enum {
EVENT_CALLBACK = 1,
EVENT_SET_CALLBACK = 2,
+ EVENT_FRAME_RENDERED = 3,
};
static struct CryptoErrorCodes {
@@ -226,6 +227,18 @@ void JMediaCodec::deleteJavaObjects(JNIEnv *env) {
mByteBufferLimitMethodID = NULL;
}
+status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
+ if (enable) {
+ if (mOnFrameRenderedNotification == NULL) {
+ mOnFrameRenderedNotification = new AMessage(kWhatFrameRendered, this);
+ }
+ } else {
+ mOnFrameRenderedNotification.clear();
+ }
+
+ return mCodec->setOnFrameRenderedNotification(mOnFrameRenderedNotification);
+}
+
status_t JMediaCodec::setCallback(jobject cb) {
if (cb != NULL) {
if (mCallbackNotification == NULL) {
@@ -728,6 +741,27 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
env->DeleteLocalRef(obj);
}
+void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) {
+ int32_t arg1 = 0, arg2 = 0;
+ jobject obj = NULL;
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ sp<AMessage> data;
+ CHECK(msg->findMessage("data", &data));
+
+ status_t err = ConvertMessageToMap(env, data, &obj);
+ if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ env->CallVoidMethod(
+ mObject, gFields.postEventFromNativeID,
+ EVENT_FRAME_RENDERED, arg1, arg2, obj);
+
+ env->DeleteLocalRef(obj);
+}
+
void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatCallbackNotify:
@@ -735,6 +769,11 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
handleCallback(msg);
break;
}
+ case kWhatFrameRendered:
+ {
+ handleFrameRenderedNotification(msg);
+ break;
+ }
default:
TRESPASS();
}
@@ -848,6 +887,22 @@ static jint throwExceptionAsNecessary(
}
}
+static void android_media_MediaCodec_native_enableOnFrameRenderedListener(
+ JNIEnv *env,
+ jobject thiz,
+ jboolean enabled) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ status_t err = codec->enableOnFrameRenderedListener(enabled);
+
+ throwExceptionAsNecessary(env, err);
+}
+
static void android_media_MediaCodec_native_setCallback(
JNIEnv *env,
jobject thiz,
@@ -1744,6 +1799,9 @@ static JNINativeMethod gMethods[] = {
{ "native_setInputSurface", "(Landroid/view/Surface;)V",
(void *)android_media_MediaCodec_setInputSurface },
+ { "native_enableOnFrameRenderedListener", "(Z)V",
+ (void *)android_media_MediaCodec_native_enableOnFrameRenderedListener },
+
{ "native_setCallback",
"(Landroid/media/MediaCodec$Callback;)V",
(void *)android_media_MediaCodec_native_setCallback },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index a4ed67b4e90c..6650cf9254e1 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -46,6 +46,8 @@ struct JMediaCodec : public AHandler {
void registerSelf();
void release();
+ status_t enableOnFrameRenderedListener(jboolean enable);
+
status_t setCallback(jobject cb);
status_t configure(
@@ -116,11 +118,11 @@ protected:
virtual ~JMediaCodec();
virtual void onMessageReceived(const sp<AMessage> &msg);
- void handleCallback(const sp<AMessage> &msg);
private:
enum {
kWhatCallbackNotify,
+ kWhatFrameRendered,
};
jclass mClass;
@@ -139,6 +141,7 @@ private:
sp<MediaCodec> mCodec;
sp<AMessage> mCallbackNotification;
+ sp<AMessage> mOnFrameRenderedNotification;
status_t mInitStatus;
@@ -148,6 +151,8 @@ private:
void cacheJavaObjects(JNIEnv *env);
void deleteJavaObjects(JNIEnv *env);
+ void handleCallback(const sp<AMessage> &msg);
+ void handleFrameRenderedNotification(const sp<AMessage> &msg);
DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
};
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8a3aa5f7c3d1..0e0584f8e0c2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -801,6 +801,9 @@
<!-- Accessibility label for the button that opens the user switcher and announces the current user. -->
<string name="accessibility_multi_user_switch_switcher_with_current">Switch user, current user <xliff:g id="current_user_name" example="John Doe">%s</xliff:g></string>
+ <!-- Accessibility label for the user icon on the lock screen. -->
+ <string name="accessibility_multi_user_switch_inactive">Current user <xliff:g id="current_user_name" example="John Doe">%s</xliff:g></string>
+
<!-- Accessibility label for the button that opens the quick contact of the user. -->
<string name="accessibility_multi_user_switch_quick_contact">Show profile</string>
@@ -1090,4 +1093,7 @@
<!-- Accessibility label for Quick Settings detail screens [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_detail">Quick Settings, <xliff:g id="title" example="Wi-Fi">%s</xliff:g>.</string>
+ <!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] -->
+ <string name="accessibility_status_bar_hotspot">Hotspot</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 6888d0e2487e..6acd13731b27 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -22,10 +22,8 @@ import static javax.microedition.khronos.egl.EGL10.*;
import android.app.ActivityManager;
import android.app.WallpaperManager;
import android.content.ComponentCallbacks2;
-import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region.Op;
@@ -35,6 +33,7 @@ import android.renderscript.Matrix4f;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -111,6 +110,9 @@ public class ImageWallpaper extends WallpaperService {
float mYOffset = 0.5f;
float mScale = 1f;
+ private Display mDefaultDisplay;
+ private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+
boolean mVisible = true;
boolean mRedrawNeeded;
boolean mOffsetsChanged;
@@ -172,7 +174,9 @@ public class ImageWallpaper extends WallpaperService {
super.onCreate(surfaceHolder);
- updateSurfaceSize(surfaceHolder);
+ mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
+
+ updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo());
setOffsetNotificationsEnabled(false);
}
@@ -184,9 +188,7 @@ public class ImageWallpaper extends WallpaperService {
mWallpaperManager.forgetLoadedWallpaper();
}
- void updateSurfaceSize(SurfaceHolder surfaceHolder) {
- Point p = getDefaultDisplaySize();
-
+ void updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo) {
// Load background image dimensions, if we haven't saved them yet
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Need to load the image to get dimensions
@@ -194,14 +196,14 @@ public class ImageWallpaper extends WallpaperService {
updateWallpaperLocked();
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Default to the display size if we can't find the dimensions
- mBackgroundWidth = p.x;
- mBackgroundHeight = p.y;
+ mBackgroundWidth = displayInfo.logicalWidth;
+ mBackgroundHeight = displayInfo.logicalHeight;
}
}
// Force the wallpaper to cover the screen in both dimensions
- int surfaceWidth = Math.max(p.x, mBackgroundWidth);
- int surfaceHeight = Math.max(p.y, mBackgroundHeight);
+ int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
+ int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
// If the surface dimensions haven't changed, then just return
final Rect frame = surfaceHolder.getSurfaceFrame();
@@ -297,26 +299,22 @@ public class ImageWallpaper extends WallpaperService {
drawFrame();
}
- private Point getDefaultDisplaySize() {
- Point p = new Point();
- Context c = ImageWallpaper.this.getApplicationContext();
- WindowManager wm = (WindowManager)c.getSystemService(Context.WINDOW_SERVICE);
- Display d = wm.getDefaultDisplay();
- d.getRealSize(p);
- return p;
+ private DisplayInfo getDefaultDisplayInfo() {
+ mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
+ return mTmpDisplayInfo;
}
void drawFrame() {
try {
- int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
- getDefaultDisplay().getRotation();
+ DisplayInfo displayInfo = getDefaultDisplayInfo();
+ int newRotation = displayInfo.rotation;
// Sometimes a wallpaper is not large enough to cover the screen in one dimension.
// Call updateSurfaceSize -- it will only actually do the update if the dimensions
// should change
if (newRotation != mLastRotation) {
// Update surface size (if necessary)
- updateSurfaceSize(getSurfaceHolder());
+ updateSurfaceSize(getSurfaceHolder(), displayInfo);
}
SurfaceHolder sh = getSurfaceHolder();
final Rect frame = sh.getSurfaceFrame();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index f129288325f5..3e122dae044e 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -9,14 +9,15 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
@@ -56,6 +57,8 @@ public class AssistManager {
private final PhoneStatusBar mBar;
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
+ private ComponentName mAssistComponent;
+
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -78,12 +81,24 @@ public class AssistManager {
}
};
+ private final ContentObserver mAssistSettingsObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAssistInfo();
+ }
+ };
+
public AssistManager(PhoneStatusBar bar, Context context) {
mContext = context;
mBar = bar;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false,
+ mAssistSettingsObserver);
+ mAssistSettingsObserver.onChange(false);
}
public void onConfigurationChanged() {
@@ -108,16 +123,17 @@ public class AssistManager {
}
public void onGestureInvoked(boolean vibrate) {
- boolean isVoiceInteractorActive = getVoiceInteractorSupportsAssistGesture();
- if (!isVoiceInteractorActive && !isAssistantIntentAvailable()) {
+ if (mAssistComponent == null) {
return;
}
+
if (vibrate) {
vibrate();
}
- if (!isVoiceInteractorActive || !isVoiceSessionRunning()) {
+ final boolean isService = isAssistantService();
+ if (isService || !isVoiceSessionRunning()) {
showOrb();
- mView.postDelayed(mHideRunnable, isVoiceInteractorActive
+ mView.postDelayed(mHideRunnable, isService
? TIMEOUT_SERVICE
: TIMEOUT_ACTIVITY);
}
@@ -157,10 +173,12 @@ public class AssistManager {
}
private void startAssist() {
- if (getVoiceInteractorSupportsAssistGesture()) {
- startVoiceInteractor();
- } else {
- startAssistActivity();
+ if (mAssistComponent != null) {
+ if (isAssistantService()) {
+ startVoiceInteractor();
+ } else {
+ startAssistActivity();
+ }
}
}
@@ -178,6 +196,9 @@ public class AssistManager {
if (intent == null) {
return;
}
+ if (mAssistComponent != null) {
+ intent.setComponent(mAssistComponent);
+ }
try {
final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
@@ -255,19 +276,9 @@ public class AssistManager {
}
private void maybeSwapSearchIcon() {
- Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
- ComponentName component = null;
- boolean isService = false;
- if (getVoiceInteractorSupportsAssistGesture()) {
- component = getVoiceInteractorComponentName();
- isService = true;
- } else if (intent != null) {
- component = intent.getComponent();
- }
- if (component != null) {
- replaceDrawable(mView.getOrb().getLogo(), component, ASSIST_ICON_METADATA_NAME,
- isService);
+ if (mAssistComponent != null) {
+ replaceDrawable(mView.getOrb().getLogo(), mAssistComponent, ASSIST_ICON_METADATA_NAME,
+ isAssistantService());
} else {
mView.getOrb().getLogo().setImageDrawable(null);
}
@@ -308,8 +319,32 @@ public class AssistManager {
mView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- public boolean isAssistantIntentAvailable() {
- return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
+ private boolean isAssistantService() {
+ return mAssistComponent == null ?
+ false : mAssistComponent.equals(getVoiceInteractorComponentName());
+ }
+
+ private void updateAssistInfo() {
+ final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ASSISTANT, UserHandle.USER_CURRENT);
+ if (setting != null) {
+ mAssistComponent = ComponentName.unflattenFromString(setting);
+ return;
+ }
+
+ // Fallback to keep backward compatible behavior when there is no user setting.
+ if (getVoiceInteractorSupportsAssistGesture()) {
+ mAssistComponent = getVoiceInteractorComponentName();
+ return;
+ }
+
+ Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
+ if (intent != null) {
+ mAssistComponent = intent.getComponent();
+ return;
+ }
+
+ mAssistComponent = null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index f59e8646d11a..ca38528b05d0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -110,15 +110,13 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
}
private void handleRefreshState() {
- boolean hasDeviceOwner = mSecurityController.hasDeviceOwner();
- boolean hasVpn = mSecurityController.isVpnEnabled();
-
- mIsVisible = (hasVpn || hasDeviceOwner);
- mIsIconVisible = hasVpn;
- if (hasDeviceOwner) {
+ mIsIconVisible = mSecurityController.isVpnEnabled();
+ if (mSecurityController.hasDeviceOwner()) {
mFooterTextId = R.string.device_owned_footer;
+ mIsVisible = true;
} else {
mFooterTextId = R.string.vpn_footer;
+ mIsVisible = mIsIconVisible;
}
mMainHandler.post(mUpdateDisplayState);
}
@@ -132,15 +130,17 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
}
private void createDialog() {
- boolean hasDeviceOwner = mSecurityController.hasDeviceOwner();
- boolean hasProfile = mSecurityController.hasProfileOwner();
- boolean hasVpn = mSecurityController.isVpnEnabled();
+ String deviceOwner = mSecurityController.getDeviceOwnerName();
+ String profileOwner = mSecurityController.getProfileOwnerName();
+ String primaryVpn = mSecurityController.getPrimaryVpnName();
+ String profileVpn = mSecurityController.getProfileVpnName();
+ boolean managed = mSecurityController.hasProfileOwner();
mDialog = new SystemUIDialog(mContext);
- mDialog.setTitle(getTitle(hasDeviceOwner, hasProfile));
- mDialog.setMessage(getMessage(hasDeviceOwner, hasProfile, hasVpn));
+ mDialog.setTitle(getTitle(deviceOwner));
+ mDialog.setMessage(getMessage(deviceOwner, profileOwner, primaryVpn, profileVpn, managed));
mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
- if (hasVpn) {
+ if (mSecurityController.isVpnEnabled()) {
mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
}
mDialog.show();
@@ -154,31 +154,42 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
return mContext.getString(R.string.quick_settings_done);
}
- private String getMessage(boolean hasDeviceOwner, boolean hasProfile, boolean hasVpn) {
- if (hasDeviceOwner) {
- if (hasVpn) {
- return mContext.getString(R.string.monitoring_description_vpn_device_owned,
- mSecurityController.getDeviceOwnerName());
+ private String getMessage(String deviceOwner, String profileOwner, String primaryVpn,
+ String profileVpn, boolean primaryUserIsManaged) {
+ if (deviceOwner != null) {
+ if (primaryVpn != null) {
+ return mContext.getString(R.string.monitoring_description_vpn_app_device_owned,
+ deviceOwner, primaryVpn);
} else {
return mContext.getString(R.string.monitoring_description_device_owned,
- mSecurityController.getDeviceOwnerName());
+ deviceOwner);
+ }
+ } else if (primaryVpn != null) {
+ if (profileVpn != null) {
+ return mContext.getString(R.string.monitoring_description_app_personal_work,
+ profileOwner, profileVpn, primaryVpn);
+ } else {
+ return mContext.getString(R.string.monitoring_description_app_personal,
+ primaryVpn);
}
- } else if (hasProfile) {
- return mContext.getString(
- R.string.monitoring_description_vpn_profile_owned,
- mSecurityController.getProfileOwnerName());
+ } else if (profileVpn != null) {
+ return mContext.getString(R.string.monitoring_description_app_work,
+ profileOwner, profileVpn);
+ } else if (profileOwner != null && primaryUserIsManaged) {
+ return mContext.getString(R.string.monitoring_description_device_owned,
+ profileOwner);
} else {
- return mContext.getString(R.string.monitoring_description_vpn);
+ // No device owner, no personal VPN, no work VPN, no user owner. Why are we here?
+ return null;
}
}
- private int getTitle(boolean hasDeviceOwner, boolean hasProfile) {
- if (hasDeviceOwner) {
+ private int getTitle(String deviceOwner) {
+ if (deviceOwner != null) {
return R.string.monitoring_title_device_owned;
- } else if (hasProfile) {
- return R.string.monitoring_title_profile_owned;
+ } else {
+ return R.string.monitoring_title;
}
- return R.string.monitoring_title;
}
private final Runnable mUpdateDisplayState = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 7d2b5c8776d1..442af9060b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -365,7 +365,7 @@ public class Recents extends SystemUI
void preloadRecentsInternal() {
// Preload only the raw task list into a new load plan (which will be consumed by the
- // RecentsActivity)
+ // RecentsActivity) only if there is a task to animate to.
ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
MutableBoolean topTaskHome = new MutableBoolean(true);
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -374,8 +374,10 @@ public class Recents extends SystemUI
sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
- preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
- topTaskHome.value);
+ if (top.getTaskCount() > 0) {
+ preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
+ topTaskHome.value);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
index 00665f4ca90d..a323684f54c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissViewButton.java
@@ -48,15 +48,14 @@ public class DismissViewButton extends Button {
public DismissViewButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getResources().getDrawable(
+ mAnimatedDismissDrawable = (AnimatedVectorDrawable) getContext().getDrawable(
R.drawable.dismiss_all_shape_animation).mutate();
mAnimatedDismissDrawable.setCallback(this);
mAnimatedDismissDrawable.setBounds(0,
0,
mAnimatedDismissDrawable.getIntrinsicWidth(),
mAnimatedDismissDrawable.getIntrinsicHeight());
- mStaticDismissDrawable = getContext().getResources().getDrawable(
- R.drawable.dismiss_all_shape);
+ mStaticDismissDrawable = getContext().getDrawable(R.drawable.dismiss_all_shape);
mStaticDismissDrawable.setBounds(0,
0,
mStaticDismissDrawable.getIntrinsicWidth(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 13b3898ed9ff..b93fc760ce2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -34,6 +34,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
import java.text.NumberFormat;
@@ -140,6 +141,10 @@ public class KeyguardStatusBarView extends RelativeLayout
((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
}
+ public void setUserSwitcherController(UserSwitcherController controller) {
+ mMultiUserSwitch.setUserSwitcherController(controller);
+ }
+
public void setUserInfoController(UserInfoController userInfoController) {
userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index f11d83cc6605..e70d146119f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -24,7 +24,7 @@ import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.systemui.R;
@@ -40,10 +40,14 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
private QSPanel mQsPanel;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private boolean mKeyguardMode;
+ private UserSwitcherController.BaseUserAdapter mUserListener;
+
final UserManager mUserManager;
private final int[] mTmpInt2 = new int[2];
+ private UserSwitcherController mUserSwitcherController;
+
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
mUserManager = UserManager.get(getContext());
@@ -53,10 +57,18 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
protected void onFinishInflate() {
super.onFinishInflate();
setOnClickListener(this);
+ refreshContentDescription();
}
public void setQsPanel(QSPanel qsPanel) {
mQsPanel = qsPanel;
+ setUserSwitcherController(qsPanel.getHost().getUserSwitcherController());
+ }
+
+ public void setUserSwitcherController(UserSwitcherController userSwitcherController) {
+ mUserSwitcherController = userSwitcherController;
+ registerListener();
+ refreshContentDescription();
}
public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
@@ -65,6 +77,28 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
public void setKeyguardMode(boolean keyguardShowing) {
mKeyguardMode = keyguardShowing;
+ registerListener();
+ }
+
+ private void registerListener() {
+ if (UserSwitcherController.isUserSwitcherAvailable(mUserManager) && mUserListener == null) {
+
+ final UserSwitcherController controller = mUserSwitcherController;
+ if (controller != null) {
+ mUserListener = new UserSwitcherController.BaseUserAdapter(controller) {
+ @Override
+ public void notifyDataSetChanged() {
+ refreshContentDescription();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return null;
+ }
+ };
+ refreshContentDescription();
+ }
+ }
}
@Override
@@ -74,22 +108,16 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
if (mKeyguardUserSwitcher != null) {
mKeyguardUserSwitcher.show(true /* animate */);
}
- } else {
- if (mQsPanel != null) {
- UserSwitcherController userSwitcherController =
- mQsPanel.getHost().getUserSwitcherController();
- if (userSwitcherController != null) {
- View center = getChildCount() > 0 ? getChildAt(0) : this;
-
- center.getLocationInWindow(mTmpInt2);
- mTmpInt2[0] += center.getWidth() / 2;
- mTmpInt2[1] += center.getHeight() / 2;
-
- mQsPanel.showDetailAdapter(true,
- userSwitcherController.userDetailAdapter,
- mTmpInt2);
- }
- }
+ } else if (mQsPanel != null && mUserSwitcherController != null) {
+ View center = getChildCount() > 0 ? getChildAt(0) : this;
+
+ center.getLocationInWindow(mTmpInt2);
+ mTmpInt2[0] += center.getWidth() / 2;
+ mTmpInt2[1] += center.getHeight() / 2;
+
+ mQsPanel.showDetailAdapter(true,
+ mUserSwitcherController.userDetailAdapter,
+ mTmpInt2);
}
} else {
Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
@@ -100,20 +128,21 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void setClickable(boolean clickable) {
+ super.setClickable(clickable);
+ refreshContentDescription();
+ }
+ private void refreshContentDescription() {
+ String currentUser = null;
+ if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)
+ && mUserSwitcherController != null) {
+ currentUser = mUserSwitcherController.getCurrentUserName(mContext);
+ }
+
+ String text = null;
if (isClickable()) {
- String text;
if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)) {
- String currentUser = null;
- if (mQsPanel != null) {
- UserSwitcherController controller = mQsPanel.getHost()
- .getUserSwitcherController();
- if (controller != null) {
- currentUser = controller.getCurrentUserName(mContext);
- }
- }
if (TextUtils.isEmpty(currentUser)) {
text = mContext.getString(R.string.accessibility_multi_user_switch_switcher);
} else {
@@ -124,11 +153,17 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
} else {
text = mContext.getString(R.string.accessibility_multi_user_switch_quick_contact);
}
- if (!TextUtils.isEmpty(text)) {
- event.getText().add(text);
+ } else {
+ if (!TextUtils.isEmpty(currentUser)) {
+ text = mContext.getString(
+ R.string.accessibility_multi_user_switch_inactive,
+ currentUser);
}
}
+ if (!TextUtils.equals(getContentDescription(), text)) {
+ setContentDescription(text);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 569b9188d17e..122e78cb90d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -858,6 +858,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// User info. Trigger first load.
mHeader.setUserInfoController(mUserInfoController);
mKeyguardStatusBar.setUserInfoController(mUserInfoController);
+ mKeyguardStatusBar.setUserSwitcherController(mUserSwitcherController);
mUserInfoController.reloadUserInfo();
mHeader.setBatteryController(mBatteryController);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 0872e0633656..6a6266e5f888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -162,7 +162,8 @@ public class PhoneStatusBarPolicy {
mCast.addCallback(mCastCallback);
// hotspot
- mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0, null);
+ mService.setIcon(SLOT_HOTSPOT, R.drawable.stat_sys_hotspot, 0,
+ mContext.getString(R.string.accessibility_status_bar_hotspot));
mService.setIconVisibility(SLOT_HOTSPOT, mHotspot.isHotspotEnabled());
mHotspot.addCallback(mHotspotCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index e1e022d8ac77..40984d477cff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -22,6 +22,8 @@ public interface SecurityController {
String getDeviceOwnerName();
String getProfileOwnerName();
boolean isVpnEnabled();
+ String getPrimaryVpnName();
+ String getProfileVpnName();
void onUserSwitched(int newUserId);
void addCallback(SecurityControllerCallback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4f47cc6943d1..962000a60622 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -36,6 +36,7 @@ import android.util.SparseArray;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnInfo;
+import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -61,7 +62,7 @@ public class SecurityControllerImpl implements SecurityController {
private final ArrayList<SecurityControllerCallback> mCallbacks
= new ArrayList<SecurityControllerCallback>();
- private SparseArray<Boolean> mCurrentVpnUsers = new SparseArray<>();
+ private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
private int mCurrentUserId;
public SecurityControllerImpl(Context context) {
@@ -82,7 +83,16 @@ public class SecurityControllerImpl implements SecurityController {
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("SecurityController state:");
- pw.print(" mCurrentVpnUsers=" + mCurrentVpnUsers);
+ pw.print(" mCurrentVpns={");
+ for (int i = 0 ; i < mCurrentVpns.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ pw.print(mCurrentVpns.keyAt(i));
+ pw.print('=');
+ pw.print(mCurrentVpns.valueAt(i).user);
+ }
+ pw.println("}");
}
@Override
@@ -97,11 +107,7 @@ public class SecurityControllerImpl implements SecurityController {
@Override
public boolean hasProfileOwner() {
- boolean result = false;
- for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) {
- result |= (mDevicePolicyManager.getProfileOwnerAsUser(profile.id) != null);
- }
- return result;
+ return mDevicePolicyManager.getProfileOwnerAsUser(mCurrentUserId) != null;
}
@Override
@@ -116,8 +122,37 @@ public class SecurityControllerImpl implements SecurityController {
}
@Override
+ public String getPrimaryVpnName() {
+ VpnConfig cfg = mCurrentVpns.get(mCurrentUserId);
+ if (cfg != null) {
+ return getNameForVpnConfig(cfg, new UserHandle(mCurrentUserId));
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String getProfileVpnName() {
+ for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) {
+ if (profile.id == mCurrentUserId) {
+ continue;
+ }
+ VpnConfig cfg = mCurrentVpns.get(profile.id);
+ if (cfg != null) {
+ return getNameForVpnConfig(cfg, profile.getUserHandle());
+ }
+ }
+ return null;
+ }
+
+ @Override
public boolean isVpnEnabled() {
- return mCurrentVpnUsers.get(mCurrentUserId) != null;
+ for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) {
+ if (mCurrentVpns.get(profile.id) != null) {
+ return true;
+ }
+ }
+ return false;
}
@Override
@@ -140,6 +175,22 @@ public class SecurityControllerImpl implements SecurityController {
fireCallbacks();
}
+ private String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
+ if (cfg.legacy) {
+ return mContext.getString(R.string.legacy_vpn_name);
+ }
+ // The package name for an active VPN is stored in the 'user' field of its VpnConfig
+ final String vpnPackage = cfg.user;
+ try {
+ Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(),
+ 0 /* flags */, user);
+ return VpnConfig.getVpnLabel(userContext, vpnPackage).toString();
+ } catch (NameNotFoundException nnfe) {
+ Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe);
+ return null;
+ }
+ }
+
private void fireCallbacks() {
for (SecurityControllerCallback callback : mCallbacks) {
callback.onStateChanged();
@@ -148,21 +199,20 @@ public class SecurityControllerImpl implements SecurityController {
private void updateState() {
// Find all users with an active VPN
- SparseArray<Boolean> vpnUsers = new SparseArray<>();
+ SparseArray<VpnConfig> vpns = new SparseArray<>();
try {
- for (VpnInfo vpn : mConnectivityManagerService.getAllVpnInfo()) {
- UserInfo user = mUserManager.getUserInfo(UserHandle.getUserId(vpn.ownerUid));
- int groupId = (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID ?
- user.profileGroupId : user.id);
-
- vpnUsers.put(groupId, Boolean.TRUE);
+ for (UserInfo user : mUserManager.getUsers()) {
+ VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id);
+ if (cfg != null) {
+ vpns.put(user.id, cfg);
+ }
}
} catch (RemoteException rme) {
// Roll back to previous state
Log.e(TAG, "Unable to list active VPNs", rme);
return;
}
- mCurrentVpnUsers = vpnUsers;
+ mCurrentVpns = vpns;
}
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@@ -182,5 +232,4 @@ public class SecurityControllerImpl implements SecurityController {
fireCallbacks();
};
};
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 7472af9a3646..2b76c31ca90b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -315,6 +315,16 @@ public class QsTuner extends Fragment implements Callback {
}
@Override
+ public String getPrimaryVpnName() {
+ return null;
+ }
+
+ @Override
+ public String getProfileVpnName() {
+ return null;
+ }
+
+ @Override
public void onUserSwitched(int newUserId) {
}
diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java
new file mode 100644
index 000000000000..6390bcd21022
--- /dev/null
+++ b/services/core/java/com/android/server/AnyMotionDetector.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.lang.Float;
+
+/**
+ * Determines if the device has been set upon a stationary object.
+ */
+public class AnyMotionDetector {
+ interface DeviceIdleCallback {
+ public void onAnyMotionResult(int result);
+ }
+
+ private static final String TAG = "AnyMotionDetector";
+
+ private static final boolean DEBUG = false;
+
+ /** Stationary status is unknown due to insufficient orientation measurements. */
+ public static final int RESULT_UNKNOWN = -1;
+
+ /** Device is stationary, e.g. still on a table. */
+ public static final int RESULT_STATIONARY = 0;
+
+ /** Device has been moved. */
+ public static final int RESULT_MOVED = 1;
+
+ /** Orientation measurements are being performed or are planned. */
+ private static final int STATE_INACTIVE = 0;
+
+ /** No orientation measurements are being performed or are planned. */
+ private static final int STATE_ACTIVE = 1;
+
+ /** Current measurement state. */
+ private int mState;
+
+ /** Threshold angle in degrees beyond which the device is considered moving. */
+ private final float THRESHOLD_ANGLE = 2f;
+
+ /** Threshold energy above which the device is considered moving. */
+ private final float THRESHOLD_ENERGY = 5f;
+
+ /** The duration of the accelerometer orientation measurement. */
+ private static final long ORIENTATION_MEASUREMENT_DURATION_MILLIS = 2500;
+
+ /** The maximum duration we will collect accelerometer data. */
+ private static final long ACCELEROMETER_DATA_TIMEOUT_MILLIS = 3000;
+
+ /** The interval between accelerometer orientation measurements. */
+ private static final long ORIENTATION_MEASUREMENT_INTERVAL_MILLIS = 5000;
+
+ /**
+ * The duration in milliseconds after which an orientation measurement is considered
+ * too stale to be used.
+ */
+ private static final int STALE_MEASUREMENT_TIMEOUT_MILLIS = 2 * 60 * 1000;
+
+ /** The accelerometer sampling interval. */
+ private static final int SAMPLING_INTERVAL_MILLIS = 40;
+
+ private AlarmManager mAlarmManager;
+ private final Handler mHandler;
+ private Intent mAlarmIntent;
+ private final Object mLock = new Object();
+ private Sensor mAccelSensor;
+ private SensorManager mSensorManager;
+ private PowerManager.WakeLock mWakeLock;
+
+ /** The time when detection was last performed. */
+ private long mDetectionStartTime;
+
+ /** The minimum number of samples required to detect AnyMotion. */
+ private int mNumSufficientSamples;
+
+ /** True if an orientation measurement is in progress. */
+ private boolean mMeasurementInProgress;
+
+ /** The most recent gravity vector. */
+ private Vector3 mCurrentGravityVector = null;
+
+ /** The second most recent gravity vector. */
+ private Vector3 mPreviousGravityVector = null;
+
+ /** Running sum of squared errors. */
+ private RunningSignalStats mRunningStats;
+
+ private DeviceIdleCallback mCallback = null;
+
+ public AnyMotionDetector(AlarmManager am, PowerManager pm, Handler handler, SensorManager sm,
+ DeviceIdleCallback callback) {
+ if (DEBUG) Slog.d(TAG, "AnyMotionDetector instantiated.");
+ mAlarmManager = am;
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mHandler = handler;
+ mSensorManager = sm;
+ mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mMeasurementInProgress = false;
+ mState = STATE_INACTIVE;
+ mCallback = callback;
+ mRunningStats = new RunningSignalStats();
+ mNumSufficientSamples = (int) Math.ceil(
+ ((double)ORIENTATION_MEASUREMENT_DURATION_MILLIS / SAMPLING_INTERVAL_MILLIS));
+ if (DEBUG) Slog.d(TAG, "mNumSufficientSamples = " + mNumSufficientSamples);
+ }
+
+ /*
+ * Acquire accel data until we determine AnyMotion status.
+ */
+ public void checkForAnyMotion() {
+ if (DEBUG) Slog.d(TAG, "checkForAnyMotion(). mState = " + mState);
+ if (mState != STATE_ACTIVE) {
+ mState = STATE_ACTIVE;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE.");
+ mCurrentGravityVector = null;
+ mPreviousGravityVector = null;
+ startOrientationMeasurement();
+ }
+ }
+
+ private void startOrientationMeasurement() {
+ if (DEBUG) Slog.d(TAG, "startOrientationMeasurement: mMeasurementInProgress=" +
+ mMeasurementInProgress + ", (mAccelSensor != null)=" + (mAccelSensor != null));
+
+ if (!mMeasurementInProgress && mAccelSensor != null) {
+ if (mSensorManager.registerListener(mListener, mAccelSensor,
+ SAMPLING_INTERVAL_MILLIS * 1000)) {
+ mWakeLock.acquire();
+ mMeasurementInProgress = true;
+ mDetectionStartTime = SystemClock.elapsedRealtime();
+ mRunningStats.reset();
+ }
+
+ Message msg = Message.obtain(mHandler, mMeasurementTimeout);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS);
+ }
+ }
+
+ private int stopOrientationMeasurementLocked() {
+ if (DEBUG) Slog.d(TAG, "stopOrientationMeasurement. mMeasurementInProgress=" +
+ mMeasurementInProgress);
+ int status = RESULT_UNKNOWN;
+ if (mMeasurementInProgress) {
+ mSensorManager.unregisterListener(mListener);
+ mHandler.removeCallbacks(mMeasurementTimeout);
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ long detectionEndTime = SystemClock.elapsedRealtime();
+ mMeasurementInProgress = false;
+ mPreviousGravityVector = mCurrentGravityVector;
+ mCurrentGravityVector = mRunningStats.getRunningAverage();
+ if (DEBUG) {
+ Slog.d(TAG, "mRunningStats = " + mRunningStats.toString());
+ String currentGravityVectorString = (mCurrentGravityVector == null) ?
+ "null" : mCurrentGravityVector.toString();
+ String previousGravityVectorString = (mPreviousGravityVector == null) ?
+ "null" : mPreviousGravityVector.toString();
+ Slog.d(TAG, "mCurrentGravityVector = " + currentGravityVectorString);
+ Slog.d(TAG, "mPreviousGravityVector = " + previousGravityVectorString);
+ }
+ mRunningStats.reset();
+ status = getStationaryStatus();
+ if (DEBUG) Slog.d(TAG, "getStationaryStatus() returned " + status);
+ if (status != RESULT_UNKNOWN) {
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE. status = " +
+ status);
+ mState = STATE_INACTIVE;
+ } else {
+ /*
+ * Unknown due to insufficient measurements. Schedule another orientation
+ * measurement.
+ */
+ if (DEBUG) Slog.d(TAG, "stopOrientationMeasurementLocked(): another measurement" +
+ " scheduled in " + ORIENTATION_MEASUREMENT_INTERVAL_MILLIS +
+ " milliseconds.");
+ Message msg = Message.obtain(mHandler, mSensorRestart);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ORIENTATION_MEASUREMENT_INTERVAL_MILLIS);
+ }
+ }
+ return status;
+ }
+
+ /*
+ * Updates mStatus to the current AnyMotion status.
+ */
+ public int getStationaryStatus() {
+ if ((mPreviousGravityVector == null) || (mCurrentGravityVector == null)) {
+ return RESULT_UNKNOWN;
+ }
+ Vector3 previousGravityVectorNormalized = mPreviousGravityVector.normalized();
+ Vector3 currentGravityVectorNormalized = mCurrentGravityVector.normalized();
+ float angle = previousGravityVectorNormalized.angleBetween(currentGravityVectorNormalized);
+ if (DEBUG) Slog.d(TAG, "getStationaryStatus: angle = " + angle);
+ if ((angle < THRESHOLD_ANGLE) && (mRunningStats.getEnergy() < THRESHOLD_ENERGY)) {
+ return RESULT_STATIONARY;
+ } else if (Float.isNaN(angle)) {
+ /**
+ * Floating point rounding errors have caused the angle calcuation's dot product to
+ * exceed 1.0. In such case, we report RESULT_MOVED to prevent devices from rapidly
+ * retrying this measurement.
+ */
+ return RESULT_MOVED;
+ }
+ long diffTime = mCurrentGravityVector.timeMillisSinceBoot -
+ mPreviousGravityVector.timeMillisSinceBoot;
+ if (diffTime > STALE_MEASUREMENT_TIMEOUT_MILLIS) {
+ if (DEBUG) Slog.d(TAG, "getStationaryStatus: mPreviousGravityVector is too stale at " +
+ diffTime + " ms ago. Returning RESULT_UNKNOWN.");
+ return RESULT_UNKNOWN;
+ }
+ return RESULT_MOVED;
+ }
+
+ private final SensorEventListener mListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int status = RESULT_UNKNOWN;
+ synchronized (mLock) {
+ Vector3 accelDatum = new Vector3(SystemClock.elapsedRealtime(), event.values[0],
+ event.values[1], event.values[2]);
+ mRunningStats.accumulate(accelDatum);
+
+ // If we have enough samples, stop accelerometer data acquisition.
+ if (mRunningStats.getSampleCount() >= mNumSufficientSamples) {
+ status = stopOrientationMeasurementLocked();
+ }
+ }
+ if (status != RESULT_UNKNOWN) {
+ mCallback.onAnyMotionResult(status);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ };
+
+ private final Runnable mSensorRestart = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ startOrientationMeasurement();
+ }
+ }
+ };
+
+ private final Runnable mMeasurementTimeout = new Runnable() {
+ @Override
+ public void run() {
+ int status = RESULT_UNKNOWN;
+ synchronized (mLock) {
+ if (DEBUG) Slog.i(TAG, "mMeasurementTimeout. Failed to collect sufficient accel " +
+ "data within " + ACCELEROMETER_DATA_TIMEOUT_MILLIS + " ms. Stopping " +
+ "orientation measurement.");
+ status = stopOrientationMeasurementLocked();
+ }
+ if (status != RESULT_UNKNOWN) {
+ mCallback.onAnyMotionResult(status);
+ }
+ }
+ };
+
+ /**
+ * A timestamped three dimensional vector and some vector operations.
+ */
+ private static class Vector3 {
+ public long timeMillisSinceBoot;
+ public float x;
+ public float y;
+ public float z;
+
+ public Vector3(long timeMillisSinceBoot, float x, float y, float z) {
+ this.timeMillisSinceBoot = timeMillisSinceBoot;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ private float norm() {
+ return (float) Math.sqrt(dotProduct(this));
+ }
+
+ private Vector3 normalized() {
+ float mag = norm();
+ return new Vector3(timeMillisSinceBoot, x / mag, y / mag, z / mag);
+ }
+
+ /**
+ * Returns the angle between this 3D vector and another given 3D vector.
+ * Assumes both have already been normalized.
+ *
+ * @param other The other Vector3 vector.
+ * @return angle between this vector and the other given one.
+ */
+ public float angleBetween(Vector3 other) {
+ double degrees = Math.toDegrees(Math.acos(this.dotProduct(other)));
+ float returnValue = (float) degrees;
+ Slog.d(TAG, "angleBetween: this = " + this.toString() +
+ ", other = " + other.toString());
+ Slog.d(TAG, " degrees = " + degrees + ", returnValue = " + returnValue);
+ return returnValue;
+ }
+
+ @Override
+ public String toString() {
+ String msg = "";
+ msg += "timeMillisSinceBoot=" + timeMillisSinceBoot;
+ msg += " | x=" + x;
+ msg += ", y=" + y;
+ msg += ", z=" + z;
+ return msg;
+ }
+
+ public float dotProduct(Vector3 v) {
+ return x * v.x + y * v.y + z * v.z;
+ }
+
+ public Vector3 times(float val) {
+ return new Vector3(timeMillisSinceBoot, x * val, y * val, z * val);
+ }
+
+ public Vector3 plus(Vector3 v) {
+ return new Vector3(v.timeMillisSinceBoot, x + v.x, y + v.y, z + v.z);
+ }
+
+ public Vector3 minus(Vector3 v) {
+ return new Vector3(v.timeMillisSinceBoot, x - v.x, y - v.y, z - v.z);
+ }
+ }
+
+ /**
+ * Maintains running statistics on the signal revelant to AnyMotion detection, including:
+ * <ul>
+ * <li>running average.
+ * <li>running sum-of-squared-errors as the energy of the signal derivative.
+ * <ul>
+ */
+ private static class RunningSignalStats {
+ Vector3 previousVector;
+ Vector3 currentVector;
+ Vector3 runningSum;
+ float energy;
+ int sampleCount;
+
+ public RunningSignalStats() {
+ reset();
+ }
+
+ public void reset() {
+ previousVector = null;
+ currentVector = null;
+ runningSum = new Vector3(0, 0, 0, 0);
+ energy = 0;
+ sampleCount = 0;
+ }
+
+ /**
+ * Apply a 3D vector v as the next element in the running SSE.
+ */
+ public void accumulate(Vector3 v) {
+ if (v == null) {
+ if (DEBUG) Slog.i(TAG, "Cannot accumulate a null vector.");
+ return;
+ }
+ sampleCount++;
+ runningSum = runningSum.plus(v);
+ previousVector = currentVector;
+ currentVector = v;
+ if (previousVector != null) {
+ Vector3 dv = currentVector.minus(previousVector);
+ float incrementalEnergy = dv.x * dv.x + dv.y * dv.y + dv.z * dv.z;
+ energy += incrementalEnergy;
+ if (DEBUG) Slog.i(TAG, "Accumulated vector " + currentVector.toString() +
+ ", runningSum = " + runningSum.toString() +
+ ", incrementalEnergy = " + incrementalEnergy +
+ ", energy = " + energy);
+ }
+ }
+
+ public Vector3 getRunningAverage() {
+ if (sampleCount > 0) {
+ return runningSum.times((float)(1.0f / sampleCount));
+ }
+ return null;
+ }
+
+ public float getEnergy() {
+ return energy;
+ }
+
+ public int getSampleCount() {
+ return sampleCount;
+ }
+
+ @Override
+ public String toString() {
+ String msg = "";
+ String currentVectorString = (currentVector == null) ?
+ "null" : currentVector.toString();
+ String previousVectorString = (previousVector == null) ?
+ "null" : previousVector.toString();
+ msg += "previousVector = " + previousVectorString;
+ msg += ", currentVector = " + currentVectorString;
+ msg += ", sampleCount = " + sampleCount;
+ msg += ", energy = " + energy;
+ return msg;
+ }
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 99c4eda62c26..3691a3ad62ee 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -112,6 +112,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
@@ -1749,6 +1750,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
return ret;
}
+ private boolean shouldPerformDiagnostics(String[] args) {
+ for (String arg : args) {
+ if (arg.equals("--diag")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1761,6 +1771,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
return;
}
+ final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
+ if (shouldPerformDiagnostics(args)) {
+ final long DIAG_TIME_MS = 5000;
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ // Start gathering diagnostic information.
+ netDiags.add(new NetworkDiagnostics(
+ nai.network,
+ new LinkProperties(nai.linkProperties),
+ DIAG_TIME_MS));
+ }
+
+ for (NetworkDiagnostics netDiag : netDiags) {
+ pw.println();
+ netDiag.waitForMeasurements();
+ netDiag.dump(pw);
+ }
+
+ return;
+ }
+
pw.print("NetworkFactories for:");
for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
pw.print(" " + nfi.name);
@@ -2994,7 +3024,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
throwIfLockdownEnabled();
synchronized(mVpns) {
- return mVpns.get(userId).prepare(oldPackage, newPackage);
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ return vpn.prepare(oldPackage, newPackage);
+ } else {
+ return false;
+ }
}
}
@@ -3016,7 +3051,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceCrossUserPermission(userId);
synchronized(mVpns) {
- mVpns.get(userId).setPackageAuthorization(packageName, authorized);
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ vpn.setPackageAuthorization(packageName, authorized);
+ }
}
}
@@ -3127,7 +3165,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
public VpnConfig getVpnConfig(int userId) {
enforceCrossUserPermission(userId);
synchronized(mVpns) {
- return mVpns.get(userId).getVpnConfig();
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ return vpn.getVpnConfig();
+ } else {
+ return null;
+ }
}
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index e9759c3e8744..4c7b52362a01 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -85,10 +85,12 @@ import java.util.Arrays;
/**
* Keeps track of device idleness and drives low power mode based on that.
*/
-public class DeviceIdleController extends SystemService {
+public class DeviceIdleController extends SystemService
+ implements AnyMotionDetector.DeviceIdleCallback {
private static final String TAG = "DeviceIdleController";
private static final boolean DEBUG = false;
+
private static final boolean COMPRESS_TIME = false;
public static final String SERVICE_NAME = "deviceidle";
@@ -96,6 +98,9 @@ public class DeviceIdleController extends SystemService {
private static final String ACTION_STEP_IDLE_STATE =
"com.android.server.device_idle.STEP_IDLE_STATE";
+ private static final String ACTION_ENTER_INACTIVE_STATE =
+ "com.android.server.device_idle.ENTER_INACTIVE_STATE";
+
// TODO: These need to be moved to system settings.
/**
@@ -104,26 +109,40 @@ public class DeviceIdleController extends SystemService {
* immediately after going inactive just because we don't want to be continually running
* the significant motion sensor whenever the screen is off.
*/
+
private static final long DEFAULT_INACTIVE_TIMEOUT = !COMPRESS_TIME ? 30*60*1000L
: 3 * 60 * 1000L;
+
+ /**
+ * If we don't receive a callback from AnyMotion in this amount of time, we will change from
+ * STATE_SENSING to STATE_INACTIVE, and any AnyMotion callbacks while not in STATE_SENSING will
+ * be ignored.
+ */
+ private static final long DEFAULT_SENSING_TIMEOUT = !DEBUG ? 5 * 60 * 1000L : 60 * 1000L;
+
/**
* This is the time, after seeing motion, that we wait after becoming inactive from
* that until we start looking for motion again.
*/
private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = !COMPRESS_TIME ? 10*60*1000L
: 60 * 1000L;
+
/**
* This is the time, after the inactive timeout elapses, that we will wait looking
* for significant motion until we truly consider the device to be idle.
*/
+
private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = !COMPRESS_TIME ? 30*60*1000L
: 3 * 60 * 1000L;
+
/**
* This is the initial time, after being idle, that we will allow ourself to be back
* in the IDLE_PENDING state allowing the system to run normally until we return to idle.
*/
+
private static final long DEFAULT_IDLE_PENDING_TIMEOUT = !COMPRESS_TIME ? 5*60*1000L
: 30 * 1000L;
+
/**
* Maximum pending idle timeout (time spent running) we will be allowed to use.
*/
@@ -138,8 +157,10 @@ public class DeviceIdleController extends SystemService {
* This is the initial time that we want to sit in the idle state before waking up
* again to return to pending idle and allowing normal work to run.
*/
+
private static final long DEFAULT_IDLE_TIMEOUT = !COMPRESS_TIME ? 60*60*1000L
: 6 * 60 * 1000L;
+
/**
* Maximum idle duration we will be allowed to use.
*/
@@ -168,9 +189,11 @@ public class DeviceIdleController extends SystemService {
private DisplayManager mDisplayManager;
private SensorManager mSensorManager;
private Sensor mSigMotionSensor;
+ private PendingIntent mSensingAlarmIntent;
private PendingIntent mAlarmIntent;
private Intent mIdleIntent;
private Display mCurDisplay;
+ private AnyMotionDetector mAnyMotionDetector;
private boolean mIdleDisabled;
private boolean mScreenOn;
private boolean mCharging;
@@ -182,15 +205,18 @@ public class DeviceIdleController extends SystemService {
private static final int STATE_INACTIVE = 1;
/** Device is past the initial inactive period, and waiting for the next idle period. */
private static final int STATE_IDLE_PENDING = 2;
+ /** Device is currently sensing motion. */
+ private static final int STATE_SENSING = 3;
/** Device is in the idle state, trying to stay asleep as much as possible. */
- private static final int STATE_IDLE = 3;
+ private static final int STATE_IDLE = 4;
/** Device is in the idle state, but temporarily out of idle to do regular maintenance. */
- private static final int STATE_IDLE_MAINTENANCE = 4;
+ private static final int STATE_IDLE_MAINTENANCE = 5;
private static String stateToString(int state) {
switch (state) {
case STATE_ACTIVE: return "ACTIVE";
case STATE_INACTIVE: return "INACTIVE";
case STATE_IDLE_PENDING: return "IDLE_PENDING";
+ case STATE_SENSING: return "SENSING";
case STATE_IDLE: return "IDLE";
case STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE";
default: return Integer.toString(state);
@@ -247,6 +273,10 @@ public class DeviceIdleController extends SystemService {
synchronized (DeviceIdleController.this) {
stepIdleStateLocked();
}
+ } else if (ACTION_ENTER_INACTIVE_STATE.equals(intent.getAction())) {
+ synchronized (DeviceIdleController.this) {
+ enterInactiveStateLocked();
+ }
}
}
};
@@ -276,6 +306,24 @@ public class DeviceIdleController extends SystemService {
}
};
+ @Override
+ public void onAnyMotionResult(int result) {
+ if (DEBUG) Slog.d(TAG, "onAnyMotionResult(" + result + ")");
+ if (mState == STATE_SENSING) {
+ if (result == AnyMotionDetector.RESULT_STATIONARY) {
+ if (DEBUG) Slog.d(TAG, "RESULT_STATIONARY received.");
+ synchronized (this) {
+ stepIdleStateLocked();
+ }
+ } else if (result == AnyMotionDetector.RESULT_MOVED) {
+ if (DEBUG) Slog.d(TAG, "RESULT_MOVED received.");
+ synchronized (this) {
+ enterInactiveStateLocked();
+ }
+ }
+ }
+ }
+
static final int MSG_WRITE_CONFIG = 1;
static final int MSG_REPORT_IDLE_ON = 2;
static final int MSG_REPORT_IDLE_OFF = 3;
@@ -288,6 +336,7 @@ public class DeviceIdleController extends SystemService {
}
@Override public void handleMessage(Message msg) {
+ if (DEBUG) Slog.d(TAG, "handleMessage(" + msg.what + ")");
switch (msg.what) {
case MSG_WRITE_CONFIG: {
handleWriteConfigFile();
@@ -452,12 +501,21 @@ public class DeviceIdleController extends SystemService {
Context.DISPLAY_SERVICE);
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ mAnyMotionDetector = new AnyMotionDetector(
+ mAlarmManager,
+ (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
+ mHandler, mSensorManager, this);
Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
.setPackage("android")
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+ Intent intentSensing = new Intent(ACTION_STEP_IDLE_STATE)
+ .setPackage("android")
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mSensingAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intentSensing, 0);
+
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -613,6 +671,7 @@ public class DeviceIdleController extends SystemService {
// because if there is anything shown we are going to be updating it at some
// frequency so can't be allowed to go into deep sleeps.
boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;;
+ if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
if (!screenOn && mScreenOn) {
mScreenOn = false;
becomeInactiveIfAppropriateLocked();
@@ -623,6 +682,7 @@ public class DeviceIdleController extends SystemService {
}
void updateChargingLocked(boolean charging) {
+ if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging);
if (!charging && mCharging) {
mCharging = false;
becomeInactiveIfAppropriateLocked();
@@ -639,6 +699,7 @@ public class DeviceIdleController extends SystemService {
}
void becomeActiveLocked(String reason) {
+ if (DEBUG) Slog.i(TAG, "becomeActiveLocked, reason = " + reason);
if (mState != STATE_ACTIVE) {
EventLogTags.writeDeviceIdle(STATE_ACTIVE, reason);
scheduleReportActiveLocked(false);
@@ -652,10 +713,12 @@ public class DeviceIdleController extends SystemService {
}
void becomeInactiveIfAppropriateLocked() {
+ if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
if (!mScreenOn && !mCharging && !mIdleDisabled && mState == STATE_ACTIVE) {
// Screen has turned off; we are now going to become inactive and start
// waiting to see if we will ultimately go idle.
mState = STATE_INACTIVE;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
scheduleAlarmLocked(mInactiveTimeout, false);
@@ -663,7 +726,17 @@ public class DeviceIdleController extends SystemService {
}
}
+ /**
+ * This is called when we've failed to receive a callback from AnyMotionDetector
+ * within the DEFAULT_SENSING_TIMEOUT, to return to STATE_INACTIVE.
+ */
+ void enterInactiveStateLocked() {
+ mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT;
+ becomeInactiveIfAppropriateLocked();
+ }
+
void stepIdleStateLocked() {
+ if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
EventLogTags.writeDeviceIdleStep();
final long now = SystemClock.elapsedRealtime();
@@ -685,26 +758,34 @@ public class DeviceIdleController extends SystemService {
mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT;
mNextIdleDelay = DEFAULT_IDLE_TIMEOUT;
mState = STATE_IDLE_PENDING;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING.");
EventLogTags.writeDeviceIdle(mState, "step");
break;
case STATE_IDLE_PENDING:
+ mState = STATE_SENSING;
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
+ scheduleSensingAlarmLocked(DEFAULT_SENSING_TIMEOUT);
+ mAnyMotionDetector.checkForAnyMotion();
+ break;
+ case STATE_SENSING:
+ cancelSensingAlarmLocked();
case STATE_IDLE_MAINTENANCE:
- // We have been waiting to become idle, and now it is time! This is the
- // only case where we want to use a wakeup alarm, because we do want to
- // drag the device out of its sleep state in this case to do the next
- // scheduled work.
scheduleAlarmLocked(mNextIdleDelay, true);
- mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR);
+ if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
+ " ms.");
+ mNextIdleDelay = (long)(mNextIdleDelay * DEFAULT_IDLE_FACTOR);
+ if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) {
mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT;
}
mState = STATE_IDLE;
- EventLogTags.writeDeviceIdle(mState, "step");
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
scheduleAlarmLocked(mNextIdlePendingDelay, false);
+ if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
+ "Next alarm in " + mNextIdlePendingDelay + " ms.");
mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR);
if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) {
mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT;
@@ -717,6 +798,7 @@ public class DeviceIdleController extends SystemService {
}
void significantMotionLocked() {
+ if (DEBUG) Slog.d(TAG, "significantMotionLocked()");
// When the sensor goes off, its trigger is automatically removed.
mSigMotionActive = false;
// The device is not yet active, so we want to go back to the pending idle
@@ -732,6 +814,7 @@ public class DeviceIdleController extends SystemService {
}
void startMonitoringSignificantMotion() {
+ if (DEBUG) Slog.d(TAG, "startMonitoringSignificantMotion()");
if (mSigMotionSensor != null && !mSigMotionActive) {
mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
mSigMotionActive = true;
@@ -739,6 +822,7 @@ public class DeviceIdleController extends SystemService {
}
void stopMonitoringSignificantMotion() {
+ if (DEBUG) Slog.d(TAG, "stopMonitoringSignificantMotion()");
if (mSigMotionActive) {
mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
mSigMotionActive = false;
@@ -752,7 +836,13 @@ public class DeviceIdleController extends SystemService {
}
}
+ void cancelSensingAlarmLocked() {
+ if (DEBUG) Slog.d(TAG, "cancelSensingAlarmLocked()");
+ mAlarmManager.cancel(mSensingAlarmIntent);
+ }
+
void scheduleAlarmLocked(long delay, boolean idleUntil) {
+ if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")");
if (mSigMotionSensor == null) {
// If there is no significant motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving. This effectively
@@ -770,6 +860,13 @@ public class DeviceIdleController extends SystemService {
}
}
+ void scheduleSensingAlarmLocked(long delay) {
+ if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
+ mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mSensingAlarmIntent);
+ }
+
private void updateWhitelistAppIdsLocked() {
mPowerSaveWhitelistAppIds.clear();
for (int i=0; i<mPowerSaveWhitelistApps.size(); i++) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 69405cfe1912..6e6fb7fbd5fc 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1323,21 +1323,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+ IInputContext inputContext, @NonNull EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
- if (attribute != null) {
- // We accept an empty package name as a valid data.
- if (!TextUtils.isEmpty(attribute.packageName) &&
- !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
- attribute.packageName)) {
- Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
- + " uid=" + cs.uid + " package=" + attribute.packageName);
- return mNoBinding;
- }
+ if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
+ attribute.packageName)) {
+ Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+ + " uid=" + cs.uid + " package=" + attribute.packageName);
+ return mNoBinding;
}
if (mCurClient != cs) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bc932685a14b..a5b0e5915ef5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -92,7 +92,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
IPhoneStateListener callback;
IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
- int callerUid;
+ int callerUserId;
int events;
@@ -100,6 +100,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+ boolean canReadPhoneState;
+
boolean matchPhoneStateListenerEvent(int events) {
return (callback != null) && ((events & this.events) != 0);
}
@@ -114,8 +116,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
+ " callback=" + callback
+ " onSubscriptionsChangedListenererCallback="
+ onSubscriptionsChangedListenerCallback
- + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId
- + " events=" + Integer.toHexString(events) + "}";
+ + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
+ + " events=" + Integer.toHexString(events)
+ + " canReadPhoneState=" + canReadPhoneState + "}";
}
}
@@ -190,13 +193,15 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private PreciseDataConnectionState mPreciseDataConnectionState =
new PreciseDataConnectionState();
- static final int PHONE_STATE_PERMISSION_MASK =
+ static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
+ PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
+ PhoneStateListener.LISTEN_VOLTE_STATE;
+
+ static final int CHECK_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_STATE |
PhoneStateListener.LISTEN_DATA_ACTIVITY |
- PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
- PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
- PhoneStateListener.LISTEN_VOLTE_STATE;;
+ PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
@@ -348,11 +353,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
@Override
public void addOnSubscriptionsChangedListener(String callingPackage,
IOnSubscriptionsChangedListener callback) {
- int callerUid = UserHandle.getCallingUserId();
- int myUid = UserHandle.myUserId();
+ int callerUserId = UserHandle.getCallingUserId();
if (VDBG) {
- log("listen oscl: E pkg=" + callingPackage + " myUid=" + myUid
- + " callerUid=" + callerUid + " callback=" + callback
+ log("listen oscl: E pkg=" + callingPackage + " myUserId=" + UserHandle.myUserId()
+ + " callerUserId=" + callerUserId + " callback=" + callback
+ " callback.asBinder=" + callback.asBinder());
}
@@ -364,7 +368,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
return;
}
- Record r = null;
+ Record r;
synchronized (mRecords) {
// register
@@ -385,8 +389,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.onSubscriptionsChangedListenerCallback = callback;
r.callingPackage = callingPackage;
- r.callerUid = callerUid;
+ r.callerUserId = callerUserId;
r.events = 0;
+ r.canReadPhoneState = true; // permission has been enforced above
if (DBG) {
log("listen oscl: Register r=" + r);
}
@@ -454,19 +459,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private void listen(String callingPackage, IPhoneStateListener callback, int events,
boolean notifyNow, int subId) {
- int callerUid = UserHandle.getCallingUserId();
- int myUid = UserHandle.myUserId();
+ int callerUserId = UserHandle.getCallingUserId();
if (VDBG) {
log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events)
- + " notifyNow=" + notifyNow + " subId=" + subId + " myUid=" + myUid
- + " callerUid=" + callerUid);
+ + " notifyNow=" + notifyNow + " subId=" + subId + " myUserId="
+ + UserHandle.myUserId() + " callerUserId=" + callerUserId);
}
if (events != PhoneStateListener.LISTEN_NONE) {
/* Checks permission and throws Security exception */
checkListenerPermission(events);
- if ((events & PHONE_STATE_PERMISSION_MASK) != 0) {
+ if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
callingPackage) != AppOpsManager.MODE_ALLOWED) {
return;
@@ -475,7 +479,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
synchronized (mRecords) {
// register
- Record r = null;
+ Record r;
find_and_add: {
IBinder b = callback.asBinder();
final int N = mRecords.size();
@@ -493,7 +497,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.callback = callback;
r.callingPackage = callingPackage;
- r.callerUid = callerUid;
+ r.callerUserId = callerUserId;
+ boolean isPhoneStateEvent = (events & (CHECK_PHONE_STATE_PERMISSION_MASK
+ | ENFORCE_PHONE_STATE_PERMISSION_MASK)) != 0;
+ r.canReadPhoneState = isPhoneStateEvent && canReadPhoneState(callingPackage);
// Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
// force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -558,7 +565,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
try {
r.callback.onCallStateChanged(mCallState[phoneId],
- mCallIncomingNumber[phoneId]);
+ getCallIncomingNumber(r, phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -638,6 +645,22 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
+ private boolean canReadPhoneState(String callingPackage) {
+ boolean canReadPhoneState = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED;
+ if (canReadPhoneState &&
+ mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ return canReadPhoneState;
+ }
+
+ private String getCallIncomingNumber(Record record, int phoneId) {
+ // Hide the number if record's process has no READ_PHONE_STATE permission
+ return record.canReadPhoneState ? mCallIncomingNumber[phoneId] : "";
+ }
+
private void remove(IBinder binder) {
synchronized (mRecords) {
final int recordCount = mRecords.size();
@@ -669,7 +692,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) &&
(r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
try {
- r.callback.onCallStateChanged(state, incomingNumber);
+ String incomingNumberOrEmpty = r.canReadPhoneState ? incomingNumber : "";
+ r.callback.onCallStateChanged(state, incomingNumberOrEmpty);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -699,7 +723,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
(r.subId == subId) &&
(r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) {
try {
- r.callback.onCallStateChanged(state, incomingNumber);
+ String incomingNumberOrEmpty = getCallIncomingNumber(r, phoneId);
+ r.callback.onCallStateChanged(state, incomingNumberOrEmpty);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1538,7 +1563,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
- if ((events & PHONE_STATE_PERMISSION_MASK) != 0) {
+ if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PHONE_STATE, null);
}
@@ -1572,10 +1597,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
- valid = r.callerUid == foregroundUser && r.matchPhoneStateListenerEvent(events);
+ valid = r.callerUserId == foregroundUser && r.matchPhoneStateListenerEvent(events);
if (DBG | DBG_LOC) {
log("validateEventsAndUserLocked: valid=" + valid
- + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
+ + " r.callerUserId=" + r.callerUserId + " foregroundUser=" + foregroundUser
+ " r.events=" + r.events + " events=" + events);
}
} finally {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bedc729d261a..0d08c2ab5ea2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -355,6 +355,10 @@ public final class ActivityManagerService extends ActivityManagerNative
// giving up on them and unfreezing the screen.
static final int USER_SWITCH_TIMEOUT = 2*1000;
+ // This is the amount of time an app needs to be running a foreground service before
+ // we will consider it to be doing interaction for usage stats.
+ static final int SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
+
// Maximum number of users we allow to be running at a time.
static final int MAX_RUNNING_USERS = 3;
@@ -10723,12 +10727,15 @@ public final class ActivityManagerService extends ActivityManagerNative
}
public void reportAssistContextExtras(IBinder token, Bundle extras, AssistStructure structure,
- AssistContent content) {
+ AssistContent content, Uri referrer) {
PendingAssistExtras pae = (PendingAssistExtras)token;
synchronized (pae) {
pae.result = extras;
pae.structure = structure;
pae.content = content;
+ if (referrer != null) {
+ pae.extras.putParcelable(Intent.EXTRA_REFERRER, referrer);
+ }
pae.haveResult = true;
pae.notifyAll();
if (pae.intent == null && pae.receiver == null) {
@@ -17752,13 +17759,13 @@ public final class ActivityManagerService extends ActivityManagerNative
// give them the best state after that.
if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
clientProcState =
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else if (mWakefulness
== PowerManagerInternal.WAKEFULNESS_AWAKE &&
(cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
!= 0) {
clientProcState =
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else {
clientProcState =
ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
@@ -17872,7 +17879,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// into the top state, since they are not on top. Instead
// give them the best state after that.
clientProcState =
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
}
}
if (procState > clientProcState) {
@@ -17912,7 +17919,7 @@ public final class ActivityManagerService extends ActivityManagerNative
case ActivityManager.PROCESS_STATE_SERVICE:
// These all are longer-term states, so pull them up to the top
// of the background states, but not all the way to the top state.
- procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
break;
default:
// Otherwise, top is a better choice, so take it.
@@ -18482,7 +18489,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
// Inform UsageStats of important process state change
// Must be called before updating setProcState
- maybeUpdateUsageStats(app);
+ maybeUpdateUsageStatsLocked(app);
app.setProcState = app.curProcState;
if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
@@ -18571,7 +18578,7 @@ public final class ActivityManagerService extends ActivityManagerNative
uidRec.pendingChange.processState = uidRec.setProcState;
}
- private void maybeUpdateUsageStats(ProcessRecord app) {
+ private void maybeUpdateUsageStatsLocked(ProcessRecord app) {
if (DEBUG_USAGE_STATS) {
Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+ "] state changes: old = " + app.setProcState + ", new = "
@@ -18580,9 +18587,32 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mUsageStatsService == null) {
return;
}
- if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
- && (app.setProcState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
- || app.setProcState < 0)) {
+ boolean isInteraction;
+ if (!mSleeping) {
+ isInteraction = app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ app.fgInteractionTime = 0;
+ } else {
+ // If the display is off, we are going to be more restrictive about what we consider
+ // to be an app interaction. Being the top activity doesn't count, nor do generally
+ // foreground services.
+ if (app.curProcState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ isInteraction = true;
+ app.fgInteractionTime = 0;
+ } else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ final long now = SystemClock.elapsedRealtime();
+ if (app.fgInteractionTime == 0) {
+ app.fgInteractionTime = now;
+ isInteraction = false;
+ } else {
+ isInteraction = now > app.fgInteractionTime + SERVICE_USAGE_INTERACTION_TIME;
+ }
+ } else {
+ isInteraction = app.curProcState
+ <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ app.fgInteractionTime = 0;
+ }
+ }
+ if (isInteraction && !app.reportedInteraction) {
String[] packages = app.getPackageList();
if (packages != null) {
for (int i = 0; i < packages.length; i++) {
@@ -18591,6 +18621,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
+ app.reportedInteraction = isInteraction;
}
private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 23e62e22eee1..577a4f94a70a 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2019,22 +2019,15 @@ public final class ActivityStackSupervisor implements DisplayListener {
r, top.task);
top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
} else {
- // A special case: we need to start the activity because it is not
- // currently running, and the caller has asked to clear the current
- // task to have this activity at the top.
+ // A special case: we need to
+ // start the activity because it is not currently
+ // running, and the caller has asked to clear the
+ // current task to have this activity at the top.
addingToTask = true;
- // Now pretend like this activity is being started by the top of its
- // task, so it is put in the right place.
+ // Now pretend like this activity is being started
+ // by the top of its task, so it is put in the
+ // right place.
sourceRecord = intentActivity;
- TaskRecord task = sourceRecord.task;
- if (task != null && task.stack == null) {
- // Target stack got cleared when we all activities were removed
- // above. Go ahead and reset it.
- targetStack = computeStackFocus(sourceRecord, false /* newTask */);
- targetStack.addTask(
- task, !launchTaskBehind /* toTop */, false /* moving */);
- }
-
}
} else if (r.realActivity.equals(intentActivity.task.realActivity)) {
// In this case the top activity on the task is the
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdfcd0c15196..0e249527a118 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -368,8 +368,11 @@ final class ProcessList {
case ActivityManager.PROCESS_STATE_TOP:
procState = "T ";
break;
+ case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ procState = "SB";
+ break;
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
- procState = "FS";
+ procState = "SF";
break;
case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
procState = "TS";
@@ -481,6 +484,7 @@ final class ProcessList {
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
+ PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
@@ -500,6 +504,7 @@ final class ProcessList {
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
@@ -519,6 +524,7 @@ final class ProcessList {
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
@@ -538,6 +544,7 @@ final class ProcessList {
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
@@ -557,6 +564,7 @@ final class ProcessList {
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 14759c39e104..3acd3a387bcc 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -112,6 +112,8 @@ final class ProcessRecord {
boolean killedByAm; // True when proc has been killed by activity manager, not for RAM
boolean killed; // True once we know the process has been killed
boolean procStateChanged; // Keep track of whether we changed 'setAdj'.
+ boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
+ long fgInteractionTime; // When we became foreground for interaction purposes
String waitingToKill; // Process is waiting to be killed when in the bg, and reason
IBinder forcingToForeground;// Token that is forcing this process to be foreground
int adjSeq; // Sequence id for identifying oom_adj assignment cycles
@@ -291,6 +293,15 @@ final class ProcessRecord {
pw.print(" foregroundServices="); pw.print(foregroundServices);
pw.print(" forcingToForeground="); pw.println(forcingToForeground);
}
+ if (reportedInteraction || fgInteractionTime != 0) {
+ pw.print(prefix); pw.print("reportedInteraction=");
+ pw.print(reportedInteraction);
+ if (fgInteractionTime != 0) {
+ pw.print(" fgInteractionTime=");
+ TimeUtils.formatDuration(fgInteractionTime, SystemClock.elapsedRealtime(), pw);
+ }
+ pw.println();
+ }
if (persistent || removed) {
pw.print(prefix); pw.print("persistent="); pw.print(persistent);
pw.print(" removed="); pw.println(removed);
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
new file mode 100644
index 000000000000..5d56d4aeb885
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.system.OsConstants.*;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.text.TextUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import libcore.io.IoUtils;
+
+
+/**
+ * NetworkDiagnostics
+ *
+ * A simple class to diagnose network connectivity fundamentals. Current
+ * checks performed are:
+ * - ICMPv4/v6 echo requests for all routers
+ * - ICMPv4/v6 echo requests for all DNS servers
+ * - DNS UDP queries to all DNS servers
+ *
+ * Currently unimplemented checks include:
+ * - report ARP/ND data about on-link neighbors
+ * - DNS TCP queries to all DNS servers
+ * - HTTP DIRECT and PROXY checks
+ * - port 443 blocking/TLS intercept checks
+ * - QUIC reachability checks
+ * - MTU checks
+ *
+ * The supplied timeout bounds the entire diagnostic process. Each specific
+ * check class must implement this upper bound on measurements in whichever
+ * manner is most appropriate and effective.
+ *
+ * @hide
+ */
+public class NetworkDiagnostics {
+ private static final String TAG = "NetworkDiagnostics";
+
+ // For brevity elsewhere.
+ private static final long now() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ // Values from RFC 1035 section 4.1.1, names from <arpa/nameser.h>.
+ // Should be a member of DnsUdpCheck, but "compiler says no".
+ public static enum DnsResponseCode { NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED };
+
+ private final Network mNetwork;
+ private final LinkProperties mLinkProperties;
+ private final Integer mInterfaceIndex;
+
+ private final long mTimeoutMs;
+ private final long mStartTime;
+ private final long mDeadlineTime;
+
+ // A counter, initialized to the total number of measurements,
+ // so callers can wait for completion.
+ private final CountDownLatch mCountDownLatch;
+
+ private class Measurement {
+ private static final String SUCCEEDED = "SUCCEEDED";
+ private static final String FAILED = "FAILED";
+
+ // TODO: Refactor to make these private for better encapsulation.
+ public String description = "";
+ public long startTime;
+ public long finishTime;
+ public String result = "";
+ public Thread thread;
+
+ public void recordSuccess(String msg) {
+ maybeFixupTimes();
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ result = SUCCEEDED + ": " + msg;
+ }
+
+ public void recordFailure(String msg) {
+ maybeFixupTimes();
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ result = FAILED + ": " + msg;
+ }
+
+ private void maybeFixupTimes() {
+ // Allows the caller to just set success/failure and not worry
+ // about also setting the correct finishing time.
+ if (finishTime == 0) { finishTime = now(); }
+
+ // In cases where, for example, a failure has occurred before the
+ // measurement even began, fixup the start time to reflect as much.
+ if (startTime == 0) { startTime = finishTime; }
+ }
+
+ @Override
+ public String toString() {
+ return description + ": " + result + " (" + (finishTime - startTime) + "ms)";
+ }
+ }
+
+ private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
+ private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
+ private final String mDescription;
+
+
+ public NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs) {
+ mNetwork = network;
+ mLinkProperties = lp;
+ mInterfaceIndex = getInterfaceIndex(mLinkProperties.getInterfaceName());
+ mTimeoutMs = timeoutMs;
+ mStartTime = now();
+ mDeadlineTime = mStartTime + mTimeoutMs;
+
+ for (RouteInfo route : mLinkProperties.getRoutes()) {
+ if (route.hasGateway()) {
+ prepareIcmpMeasurement(route.getGateway());
+ }
+ }
+ for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
+ prepareIcmpMeasurement(nameserver);
+ prepareDnsMeasurement(nameserver);
+ }
+
+ mCountDownLatch = new CountDownLatch(totalMeasurementCount());
+
+ startMeasurements();
+
+ mDescription = "ifaces{" + TextUtils.join(",", mLinkProperties.getAllInterfaceNames()) + "}"
+ + " index{" + mInterfaceIndex + "}"
+ + " network{" + mNetwork + "}"
+ + " nethandle{" + mNetwork.getNetworkHandle() + "}";
+ }
+
+ private static Integer getInterfaceIndex(String ifname) {
+ try {
+ NetworkInterface ni = NetworkInterface.getByName(ifname);
+ return ni.getIndex();
+ } catch (NullPointerException | SocketException e) {
+ return null;
+ }
+ }
+
+ private void prepareIcmpMeasurement(InetAddress target) {
+ if (!mIcmpChecks.containsKey(target)) {
+ Measurement measurement = new Measurement();
+ measurement.thread = new Thread(new IcmpCheck(target, measurement));
+ mIcmpChecks.put(target, measurement);
+ }
+ }
+
+ private void prepareDnsMeasurement(InetAddress target) {
+ if (!mDnsUdpChecks.containsKey(target)) {
+ Measurement measurement = new Measurement();
+ measurement.thread = new Thread(new DnsUdpCheck(target, measurement));
+ mDnsUdpChecks.put(target, measurement);
+ }
+ }
+
+ private int totalMeasurementCount() {
+ return mIcmpChecks.size() + mDnsUdpChecks.size();
+ }
+
+ private void startMeasurements() {
+ for (Measurement measurement : mIcmpChecks.values()) {
+ measurement.thread.start();
+ }
+ for (Measurement measurement : mDnsUdpChecks.values()) {
+ measurement.thread.start();
+ }
+ }
+
+ public void waitForMeasurements() {
+ try {
+ mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignored) {}
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println(TAG + ":" + mDescription);
+ final long unfinished = mCountDownLatch.getCount();
+ if (unfinished > 0) {
+ // This can't happen unless a caller forgets to call waitForMeasurements()
+ // or a measurement isn't implemented to correctly honor the timeout.
+ pw.println("WARNING: countdown wait incomplete: "
+ + unfinished + " unfinished measurements");
+ }
+
+ pw.increaseIndent();
+ for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet4Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet6Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet4Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet6Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ pw.decreaseIndent();
+ }
+
+
+ private class SimpleSocketCheck implements Closeable {
+ protected final InetAddress mTarget;
+ protected final int mAddressFamily;
+ protected final Measurement mMeasurement;
+ protected FileDescriptor mFileDescriptor;
+ protected SocketAddress mSocketAddress;
+
+ protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
+ mMeasurement = measurement;
+
+ if (target instanceof Inet6Address) {
+ Inet6Address targetWithScopeId = null;
+ if (target.isLinkLocalAddress() && mInterfaceIndex != null) {
+ try {
+ targetWithScopeId = Inet6Address.getByAddress(
+ null, target.getAddress(), mInterfaceIndex);
+ } catch (UnknownHostException e) {
+ mMeasurement.recordFailure(e.toString());
+ }
+ }
+ mTarget = (targetWithScopeId != null) ? targetWithScopeId : target;
+ mAddressFamily = AF_INET6;
+ } else {
+ mTarget = target;
+ mAddressFamily = AF_INET;
+ }
+ }
+
+ protected void setupSocket(
+ int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
+ throws ErrnoException, IOException {
+ mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
+ // Setting SNDTIMEO is purely for defensive purposes.
+ Os.setsockoptTimeval(mFileDescriptor,
+ SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
+ Os.setsockoptTimeval(mFileDescriptor,
+ SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout));
+ // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability.
+ mNetwork.bindSocket(mFileDescriptor);
+ Os.connect(mFileDescriptor, mTarget, dstPort);
+ mSocketAddress = Os.getsockname(mFileDescriptor);
+ }
+
+ protected String getSocketAddressString() {
+ // The default toString() implementation is not the prettiest.
+ InetSocketAddress inetSockAddr = (InetSocketAddress) mSocketAddress;
+ InetAddress localAddr = inetSockAddr.getAddress();
+ return String.format(
+ (localAddr instanceof Inet6Address ? "[%s]:%d" : "%s:%d"),
+ localAddr.getHostAddress(), inetSockAddr.getPort());
+ }
+
+ @Override
+ public void close() {
+ IoUtils.closeQuietly(mFileDescriptor);
+ }
+ }
+
+
+ private class IcmpCheck extends SimpleSocketCheck implements Runnable {
+ private static final int TIMEOUT_SEND = 100;
+ private static final int TIMEOUT_RECV = 300;
+ private static final int ICMPV4_ECHO_REQUEST = 8;
+ private static final int ICMPV6_ECHO_REQUEST = 128;
+ private static final int PACKET_BUFSIZE = 512;
+ private final int mProtocol;
+ private final int mIcmpType;
+
+ public IcmpCheck(InetAddress target, Measurement measurement) {
+ super(target, measurement);
+
+ if (mAddressFamily == AF_INET6) {
+ mProtocol = IPPROTO_ICMPV6;
+ mIcmpType = ICMPV6_ECHO_REQUEST;
+ mMeasurement.description = "ICMPv6";
+ } else {
+ mProtocol = IPPROTO_ICMP;
+ mIcmpType = ICMPV4_ECHO_REQUEST;
+ mMeasurement.description = "ICMPv4";
+ }
+
+ mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
+ }
+
+ @Override
+ public void run() {
+ // Check if this measurement has already failed during setup.
+ if (mMeasurement.finishTime > 0) {
+ // If the measurement failed during construction it didn't
+ // decrement the countdown latch; do so here.
+ mCountDownLatch.countDown();
+ return;
+ }
+
+ try {
+ setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
+ } catch (ErrnoException | IOException e) {
+ mMeasurement.recordFailure(e.toString());
+ return;
+ }
+ mMeasurement.description += " src{" + getSocketAddressString() + "}";
+
+ // Build a trivial ICMP packet.
+ final byte[] icmpPacket = {
+ (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0 // ICMP header
+ };
+
+ int count = 0;
+ mMeasurement.startTime = now();
+ while (now() < mDeadlineTime - (TIMEOUT_SEND + TIMEOUT_RECV)) {
+ count++;
+ icmpPacket[icmpPacket.length - 1] = (byte) count;
+ try {
+ Os.write(mFileDescriptor, icmpPacket, 0, icmpPacket.length);
+ } catch (ErrnoException | InterruptedIOException e) {
+ mMeasurement.recordFailure(e.toString());
+ break;
+ }
+
+ try {
+ ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
+ Os.read(mFileDescriptor, reply);
+ // TODO: send a few pings back to back to guesstimate packet loss.
+ mMeasurement.recordSuccess("1/" + count);
+ break;
+ } catch (ErrnoException | InterruptedIOException e) {
+ continue;
+ }
+ }
+ if (mMeasurement.finishTime == 0) {
+ mMeasurement.recordFailure("0/" + count);
+ }
+
+ close();
+ }
+ }
+
+
+ private class DnsUdpCheck extends SimpleSocketCheck implements Runnable {
+ private static final int TIMEOUT_SEND = 100;
+ private static final int TIMEOUT_RECV = 500;
+ private static final int DNS_SERVER_PORT = 53;
+ private static final int RR_TYPE_A = 1;
+ private static final int RR_TYPE_AAAA = 28;
+ private static final int PACKET_BUFSIZE = 512;
+
+ private final Random mRandom = new Random();
+
+ // Should be static, but the compiler mocks our puny, human attempts at reason.
+ private String responseCodeStr(int rcode) {
+ try {
+ return DnsResponseCode.values()[rcode].toString();
+ } catch (IndexOutOfBoundsException e) {
+ return String.valueOf(rcode);
+ }
+ }
+
+ private final int mQueryType;
+
+ public DnsUdpCheck(InetAddress target, Measurement measurement) {
+ super(target, measurement);
+
+ // TODO: Ideally, query the target for both types regardless of address family.
+ if (mAddressFamily == AF_INET6) {
+ mQueryType = RR_TYPE_AAAA;
+ } else {
+ mQueryType = RR_TYPE_A;
+ }
+
+ mMeasurement.description = "DNS UDP dst{" + mTarget.getHostAddress() + "}";
+ }
+
+ @Override
+ public void run() {
+ // Check if this measurement has already failed during setup.
+ if (mMeasurement.finishTime > 0) {
+ // If the measurement failed during construction it didn't
+ // decrement the countdown latch; do so here.
+ mCountDownLatch.countDown();
+ return;
+ }
+
+ try {
+ setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV, DNS_SERVER_PORT);
+ } catch (ErrnoException | IOException e) {
+ mMeasurement.recordFailure(e.toString());
+ return;
+ }
+ mMeasurement.description += " src{" + getSocketAddressString() + "}";
+
+ // This needs to be fixed length so it can be dropped into the pre-canned packet.
+ final String sixRandomDigits =
+ Integer.valueOf(mRandom.nextInt(900000) + 100000).toString();
+ mMeasurement.description += " qtype{" + mQueryType + "}"
+ + " qname{" + sixRandomDigits + "-android-ds.metric.gstatic.com}";
+
+ // Build a trivial DNS packet.
+ final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
+
+ int count = 0;
+ mMeasurement.startTime = now();
+ while (now() < mDeadlineTime - (TIMEOUT_RECV + TIMEOUT_RECV)) {
+ count++;
+ try {
+ Os.write(mFileDescriptor, dnsPacket, 0, dnsPacket.length);
+ } catch (ErrnoException | InterruptedIOException e) {
+ mMeasurement.recordFailure(e.toString());
+ break;
+ }
+
+ try {
+ ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
+ Os.read(mFileDescriptor, reply);
+ // TODO: more correct and detailed evaluation of the response,
+ // possibly adding the returned IP address(es) to the output.
+ final String rcodeStr = (reply.limit() > 3)
+ ? " " + responseCodeStr((int) (reply.get(3)) & 0x0f)
+ : "";
+ mMeasurement.recordSuccess("1/" + count + rcodeStr);
+ break;
+ } catch (ErrnoException | InterruptedIOException e) {
+ continue;
+ }
+ }
+ if (mMeasurement.finishTime == 0) {
+ mMeasurement.recordFailure("0/" + count);
+ }
+
+ close();
+ }
+
+ private byte[] getDnsQueryPacket(String sixRandomDigits) {
+ byte[] rnd = sixRandomDigits.getBytes(StandardCharsets.US_ASCII);
+ return new byte[] {
+ (byte) mRandom.nextInt(), (byte) mRandom.nextInt(), // [0-1] query ID
+ 1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD).
+ 0, 1, // [4-5] QDCOUNT (number of queries)
+ 0, 0, // [6-7] ANCOUNT (number of answers)
+ 0, 0, // [8-9] NSCOUNT (number of name server records)
+ 0, 0, // [10-11] ARCOUNT (number of additional records)
+ 17, rnd[0], rnd[1], rnd[2], rnd[3], rnd[4], rnd[5],
+ '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 's',
+ 6, 'm', 'e', 't', 'r', 'i', 'c',
+ 7, 'g', 's', 't', 'a', 't', 'i', 'c',
+ 3, 'c', 'o', 'm',
+ 0, // null terminator of FQDN (root TLD)
+ 0, (byte) mQueryType, // QTYPE
+ 0, 1 // QCLASS, set to 1 = IN (Internet)
+ };
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 604ac97838e2..e2cc3f71b793 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -278,6 +278,7 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final boolean DEBUG_VERIFY = false;
private static final boolean DEBUG_DEXOPT = false;
private static final boolean DEBUG_ABI_SELECTION = false;
+ private static final boolean DEBUG_DOMAIN_VERIFICATION = false;
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
@@ -620,7 +621,8 @@ public class PackageManagerService extends IPackageManager.Stub {
UserHandle user = new UserHandle(userId);
mContext.sendBroadcastAsUser(verificationIntent, user);
- Slog.d(TAG, "Sending IntenFilter verification broadcast");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Sending IntenFilter verification broadcast");
}
public void receiveVerificationResponse(int verificationId) {
@@ -634,8 +636,9 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.ActivityIntentInfo filter = filters.get(n);
filter.setVerified(verified);
- Slog.d(TAG, "IntentFilter " + filter.toString() + " verified with result:"
- + verified + " and hosts:" + ivs.getHostsString());
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "IntentFilter " + filter.toString()
+ + " verified with result:" + verified + " and hosts:"
+ + ivs.getHostsString());
}
mIntentFilterVerificationStates.remove(verificationId);
@@ -651,8 +654,8 @@ public class PackageManagerService extends IPackageManager.Stub {
+ verificationId + " packageName:" + packageName);
return;
}
- Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId:"
- + verificationId);
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Updating IntentFilterVerificationInfo for verificationId:" + verificationId);
synchronized (mPackages) {
if (verified) {
@@ -707,7 +710,8 @@ public class PackageManagerService extends IPackageManager.Stub {
ActivityIntentInfo filter, String packageName) {
if (!(filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
- Slog.d(TAG, "IntentFilter does not contain HTTP nor HTTPS data scheme");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "IntentFilter does not contain HTTP nor HTTPS data scheme");
return false;
}
IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
@@ -736,16 +740,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private static boolean hasValidDomains(ActivityIntentInfo filter) {
- return hasValidDomains(filter, true);
- }
-
- private static boolean hasValidDomains(ActivityIntentInfo filter, boolean logging) {
boolean hasHTTPorHTTPS = filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
filter.hasDataScheme(IntentFilter.SCHEME_HTTPS);
if (!hasHTTPorHTTPS) {
- if (logging) {
- Slog.d(TAG, "IntentFilter does not contain any HTTP or HTTPS data scheme");
- }
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "IntentFilter does not contain any HTTP or HTTPS data scheme");
return false;
}
return true;
@@ -1515,7 +1514,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final int userId = state.getUserId();
- Slog.d(TAG, "Processing IntentFilter verification with token:"
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Processing IntentFilter verification with token:"
+ verificationId + " and userId:" + userId);
final IntentFilterVerificationResponse response =
@@ -1523,20 +1523,22 @@ public class PackageManagerService extends IPackageManager.Stub {
state.setVerifierResponse(response.callerUid, response.code);
- Slog.d(TAG, "IntentFilter verification with token:" + verificationId
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "IntentFilter verification with token:" + verificationId
+ " and userId:" + userId
+ " is settings verifier response with response code:"
+ response.code);
if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
- Slog.d(TAG, "Domains failing verification: "
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Domains failing verification: "
+ response.getFailedDomainsString());
}
if (state.isVerificationComplete()) {
mIntentFilterVerifier.receiveVerificationResponse(verificationId);
} else {
- Slog.d(TAG, "IntentFilter verification with token:" + verificationId
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "IntentFilter verification with token:" + verificationId
+ " was not said to be complete");
}
@@ -2160,7 +2162,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.mFingerprint = Build.FINGERPRINT;
}
- primeDomainVerificationsLPw(false);
+ primeDomainVerificationsLPw();
checkDefaultBrowser();
// All the changes are done during package scanning.
@@ -2268,37 +2270,34 @@ public class PackageManagerService extends IPackageManager.Stub {
if (priority < info.priority) {
priority = info.priority;
verifierComponentName = new ComponentName(packageName, info.activityInfo.name);
- Slog.d(TAG, "Selecting IntentFilterVerifier: " + verifierComponentName +
- " with priority: " + info.priority);
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Selecting IntentFilterVerifier: "
+ + verifierComponentName + " with priority: " + info.priority);
}
}
return verifierComponentName;
}
- private void primeDomainVerificationsLPw(boolean logging) {
- Slog.d(TAG, "Start priming domain verifications");
+ private void primeDomainVerificationsLPw() {
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Start priming domain verifications");
boolean updated = false;
ArraySet<String> allHostsSet = new ArraySet<>();
for (PackageParser.Package pkg : mPackages.values()) {
final String packageName = pkg.packageName;
if (!hasDomainURLs(pkg)) {
- if (logging) {
- Slog.d(TAG, "No priming domain verifications for " +
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "No priming domain verifications for " +
"package with no domain URLs: " + packageName);
- }
continue;
}
if (!pkg.isSystemApp()) {
- if (logging) {
- Slog.d(TAG, "No priming domain verifications for a non system package : " +
- packageName);
- }
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "No priming domain verifications for a non system package : " +
+ packageName);
continue;
}
for (PackageParser.Activity a : pkg.activities) {
for (ActivityIntentInfo filter : a.intents) {
- if (hasValidDomains(filter, false)) {
+ if (hasValidDomains(filter)) {
allHostsSet.addAll(filter.getHostsList());
}
}
@@ -2310,25 +2309,23 @@ public class PackageManagerService extends IPackageManager.Stub {
IntentFilterVerificationInfo ivi =
mSettings.createIntentFilterVerificationIfNeededLPw(packageName, allHostsList);
if (ivi != null) {
- // We will always log this
- Slog.d(TAG, "Priming domain verifications for package: " + packageName +
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Priming domain verifications for package: " + packageName +
" with hosts:" + ivi.getDomainsString());
ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
updated = true;
}
else {
- if (logging) {
- Slog.d(TAG, "No priming domain verifications for package: " + packageName);
- }
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "No priming domain verifications for package: " + packageName);
}
allHostsSet.clear();
}
if (updated) {
- if (logging) {
- Slog.d(TAG, "Will need to write primed domain verifications");
- }
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Will need to write primed domain verifications");
}
- Slog.d(TAG, "End priming domain verifications");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "End priming domain verifications");
}
private void checkDefaultBrowser() {
@@ -10344,7 +10341,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
if (origin.staged) {
- Slog.d(TAG, origin.file + " already staged; skipping copy");
+ if (DEBUG_INSTALL) Slog.d(TAG, origin.file + " already staged; skipping copy");
codeFile = origin.file;
resourceFile = origin.file;
return PackageManager.INSTALL_SUCCEEDED;
@@ -10417,16 +10414,16 @@ public class PackageManagerService extends IPackageManager.Stub {
final File beforeCodeFile = codeFile;
final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
- Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
try {
Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
} catch (ErrnoException e) {
- Slog.d(TAG, "Failed to rename", e);
+ Slog.w(TAG, "Failed to rename", e);
return false;
}
if (!SELinux.restoreconRecursive(afterCodeFile)) {
- Slog.d(TAG, "Failed to restorecon");
+ Slog.w(TAG, "Failed to restorecon");
return false;
}
@@ -10589,7 +10586,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
if (origin.staged) {
- Slog.d(TAG, origin.cid + " already staged; skipping copy");
+ if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
cid = origin.cid;
setMountPath(PackageHelper.getSdDir(cid));
return PackageManager.INSTALL_SUCCEEDED;
@@ -10849,8 +10846,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
int copyApk(IMediaContainerService imcs, boolean temp) {
- Slog.d(TAG, "Moving " + move.packageName + " from " + move.fromUuid + " to "
- + move.toUuid);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Moving " + move.packageName + " from "
+ + move.fromUuid + " to " + move.toUuid);
synchronized (mInstaller) {
if (mInstaller.moveCompleteApp(move.fromUuid, move.toUuid, move.packageName,
move.dataAppName, move.appId, move.seinfo) != 0) {
@@ -10860,7 +10857,7 @@ public class PackageManagerService extends IPackageManager.Stub {
codeFile = new File(Environment.getDataAppDirectory(move.toUuid), move.dataAppName);
resourceFile = codeFile;
- Slog.d(TAG, "codeFile after move is " + codeFile);
+ if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + codeFile);
return PackageManager.INSTALL_SUCCEEDED;
}
@@ -11695,7 +11692,7 @@ public class PackageManagerService extends IPackageManager.Stub {
private void startIntentFilterVerifications(int userId, PackageParser.Package pkg) {
if (mIntentFilterVerifierComponent == null) {
- Slog.d(TAG, "No IntentFilter verification will not be done as "
+ Slog.w(TAG, "No IntentFilter verification will not be done as "
+ "there is no IntentFilterVerifier available!");
return;
}
@@ -11717,17 +11714,20 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Package pkg) {
int size = pkg.activities.size();
if (size == 0) {
- Slog.d(TAG, "No activity, so no need to verify any IntentFilter!");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "No activity, so no need to verify any IntentFilter!");
return;
}
final boolean hasDomainURLs = hasDomainURLs(pkg);
if (!hasDomainURLs) {
- Slog.d(TAG, "No domain URLs, so no need to verify any IntentFilter!");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "No domain URLs, so no need to verify any IntentFilter!");
return;
}
- Slog.d(TAG, "Checking for userId:" + userId + " if any IntentFilter from the " + size
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Checking for userId:" + userId
+ + " if any IntentFilter from the " + size
+ " Activities needs verification ...");
final int verificationId = mIntentFilterVerificationToken++;
@@ -11740,12 +11740,14 @@ public class PackageManagerService extends IPackageManager.Stub {
for (ActivityIntentInfo filter : a.intents) {
boolean needsFilterVerification = filter.needsVerification();
if (needsFilterVerification && needsNetworkVerificationLPr(filter)) {
- Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString());
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Verification needed for IntentFilter:" + filter.toString());
mIntentFilterVerifier.addOneIntentFilterVerification(
verifierUid, userId, verificationId, filter, packageName);
count++;
} else if (!needsFilterVerification) {
- Slog.d(TAG, "No verification needed for IntentFilter:" + filter.toString());
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "No verification needed for IntentFilter:" + filter.toString());
if (hasValidDomains(filter)) {
ArrayList<String> hosts = filter.getHostsList();
if (hosts.size() > 0) {
@@ -11757,8 +11759,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
} else {
- Slog.d(TAG, "Verification already done for IntentFilter:"
- + filter.toString());
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "Verification already done for IntentFilter:" + filter.toString());
}
}
}
@@ -11766,10 +11768,12 @@ public class PackageManagerService extends IPackageManager.Stub {
if (count > 0) {
mIntentFilterVerifier.startVerifications(userId);
- Slog.d(TAG, "Started " + count + " IntentFilter verification"
- + (count > 1 ? "s" : "") + " for userId:" + userId + "!");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Started " + count
+ + " IntentFilter verification" + (count > 1 ? "s" : "")
+ + " for userId:" + userId + "!");
} else {
- Slog.d(TAG, "No need to start any IntentFilter verification!");
+ if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
+ "No need to start any IntentFilter verification!");
if (allHosts.size() > 0 && mSettings.createIntentFilterVerificationIfNeededLPw(
packageName, allHosts) != null) {
scheduleWriteSettingsLocked();
@@ -14558,7 +14562,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- Slog.d(TAG, "Loaded packages " + loaded);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded);
sendResourcesChangedBroadcast(true, false, loaded, null);
}
@@ -14584,7 +14588,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- Slog.d(TAG, "Unloaded packages " + unloaded);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded);
sendResourcesChangedBroadcast(false, false, unloaded, null);
}
@@ -14605,7 +14609,7 @@ public class PackageManagerService extends IPackageManager.Stub {
try {
movePackageInternal(packageName, volumeUuid, moveId);
} catch (PackageManagerException e) {
- Slog.d(TAG, "Failed to move " + packageName, e);
+ Slog.w(TAG, "Failed to move " + packageName, e);
mMoveCallbacks.notifyStatusChanged(moveId,
PackageManager.MOVE_FAILED_INTERNAL_ERROR);
}
@@ -14714,7 +14718,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- Slog.d(TAG, "Measured code size " + stats.codeSize + ", data size " + stats.dataSize);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Measured code size " + stats.codeSize + ", data size "
+ + stats.dataSize);
final long startFreeBytes = measurePath.getFreeSpace();
final long sizeBytes;
@@ -14742,7 +14747,7 @@ public class PackageManagerService extends IPackageManager.Stub {
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) throws RemoteException {
- Slog.d(TAG, "Install result for move: "
+ if (DEBUG_INSTALL) Slog.d(TAG, "Install result for move: "
+ PackageManager.installStatusToString(returnCode, msg));
installedLatch.countDown();
@@ -14891,7 +14896,7 @@ public class PackageManagerService extends IPackageManager.Stub {
for (VolumeInfo vol : vols) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
final String volumeUuid = vol.getFsUuid();
- Slog.d(TAG, "Removing user data on volume " + volumeUuid);
+ if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid);
mInstaller.removeUserDataDirs(volumeUuid, userHandle);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 08d386b4e699..095b7d79a156 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -589,6 +589,10 @@ public class UserManagerService extends IUserManager.Stub {
if (ActivityManager.isLowRamDeviceStatic()) {
return false;
}
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_MANAGED_USERS)) {
+ return false;
+ }
synchronized(mPackagesLock) {
// Limit number of managed profiles that can be created
if (numberOfUsersOfTypeLocked(UserInfo.FLAG_MANAGED_PROFILE, true)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 38e27653c2ec..486105066f7e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2734,15 +2734,12 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
final AppWindowToken appToken = win.mAppToken;
- // Prevent an immediate window exit only for a real animation, ignoring e.g.
- // dummy animations.
- final boolean inAnimation = win.mWinAnimator.isWindowAnimatingNow();
// The starting window is the last window in this app token and it isn't animating.
// Allow it to be removed now as there is no additional window or animation that will
// trigger its removal.
final boolean lastWinStartingNotAnimating = startingWindow && appToken!= null
- && appToken.allAppWindows.size() == 1 && !inAnimation;
- if (!lastWinStartingNotAnimating && (win.mExiting || inAnimation)) {
+ && appToken.allAppWindows.size() == 1 && !win.mWinAnimator.isAnimating();
+ if (!lastWinStartingNotAnimating && (win.mExiting || win.mWinAnimator.isAnimating())) {
// The exit animation is running... wait for it!
win.mExiting = true;
win.mRemoveOnExit = true;
@@ -4675,7 +4672,12 @@ public class WindowManagerService extends IWindowManager.Stub
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (okToDisplay() && mAppTransition.isTransitionSet()) {
- if (!wtoken.startingDisplayed || mSkipAppTransitionAnimation) {
+ // A dummy animation is a placeholder animation which informs others that an
+ // animation is going on (in this case an application transition). If the animation
+ // was transferred from another application/animator, no dummy animator should be
+ // created since an animation is already in progress.
+ if (!wtoken.mAppAnimator.usingTransferredAnimation &&
+ (!wtoken.startingDisplayed || mSkipAppTransitionAnimation)) {
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Setting dummy animation on: " + wtoken);
wtoken.mAppAnimator.setDummyAnimation();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b42c8ebe76ef..5064d8fd3a1e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -251,21 +251,11 @@ class WindowStateAnimator {
&& mAppAnimator.animation == AppWindowAnimator.sDummyAnimation;
}
- /** Is this window currently set to animate or currently animating?
- * NOTE: The method will return true for cases where the window isn't currently animating, but
- * is set to animate. i.e. if the window animation is currently set to a dummy placeholder
- * animation. Use {@link #isWindowAnimatingNow} to know if the window is currently running a
- * real animation. */
+ /** Is this window currently set to animate or currently animating? */
boolean isWindowAnimating() {
return mAnimation != null;
}
- /** Is the window performing a real animation and not a dummy which is only waiting for an
- * an animation to start? */
- boolean isWindowAnimatingNow() {
- return isWindowAnimating() && !isDummyAnimation();
- }
-
void cancelExitAnimationForNextAnimationLocked() {
if (mAnimation != null) {
mAnimation.cancel();
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index bb210f1746cd..004241405fdc 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -838,7 +838,8 @@ public abstract class Connection extends Conferenceable {
* <p>
* This could be in response to a preview request via
* {@link #onRequestConnectionDataUsage()}, or as a periodic update by the
- * {@link VideoProvider}.
+ * {@link VideoProvider}. Where periodic updates of data usage are provided, they should be
+ * provided at most for every 1 MB of data transferred and no more than once every 10 sec.
* <p>
* Received by the {@link InCallService} via
* {@link InCallService.VideoCall.Callback#onCallDataUsageChanged(long)}.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index f7f442549cdb..fb985ceead7c 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -413,6 +413,8 @@ public abstract class InCallService extends Service {
/**
* Clears the video call callback set via {@link #registerCallback}.
+ *
+ * @param callback The video call callback to clear.
*/
public abstract void unregisterCallback(VideoCall.Callback callback);
@@ -524,7 +526,8 @@ public abstract class InCallService extends Service {
/**
* The {@link InCallService} extends this class to provide a means of receiving callbacks
- * from the {@link Connection.VideoProvider}.<p>
+ * from the {@link Connection.VideoProvider}.
+ * <p>
* When the {@link InCallService} receives the
* {@link Call.Callback#onVideoCallChanged(Call, VideoCall)} callback, it should create an
* instance its {@link VideoCall.Callback} implementation and set it on the
@@ -533,7 +536,7 @@ public abstract class InCallService extends Service {
public static abstract class Callback {
/**
* Called when the {@link Connection.VideoProvider} receives a session modification
- * request is received from the peer device.
+ * request from the peer device.
* <p>
* The {@link InCallService} may potentially prompt the user to confirm whether they
* wish to accept the request, or decide to automatically accept the request. In either
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index d62c08ed4934..8f7b82f1c395 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -22,6 +22,7 @@ import com.android.internal.telecom.IVideoProvider;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -207,29 +208,111 @@ public final class RemoteConnection {
public void onExtrasChanged(RemoteConnection connection, @Nullable Bundle extras) {}
}
+ /**
+ * {@link RemoteConnection.VideoProvider} associated with a {@link RemoteConnection}. Used to
+ * receive video related events and control the video associated with a
+ * {@link RemoteConnection}.
+ *
+ * @see Connection.VideoProvider
+ */
public static class VideoProvider {
+ /**
+ * Callback class used by the {@link RemoteConnection.VideoProvider} to relay events from
+ * the {@link Connection.VideoProvider}.
+ */
public abstract static class Callback {
+ /**
+ * Reports a session modification request received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param videoProfile The requested video call profile.
+ * @see InCallService.VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)
+ * @see Connection.VideoProvider#receiveSessionModifyRequest(VideoProfile)
+ */
public void onSessionModifyRequestReceived(
VideoProvider videoProvider,
VideoProfile videoProfile) {}
+ /**
+ * Reports a session modification response received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param status Status of the session modify request.
+ * @param requestedProfile The original request which was sent to the peer device.
+ * @param responseProfile The actual profile changes made by the peer device.
+ * @see InCallService.VideoCall.Callback#onSessionModifyResponseReceived(int,
+ * VideoProfile, VideoProfile)
+ * @see Connection.VideoProvider#receiveSessionModifyResponse(int, VideoProfile,
+ * VideoProfile)
+ */
public void onSessionModifyResponseReceived(
VideoProvider videoProvider,
int status,
VideoProfile requestedProfile,
VideoProfile responseProfile) {}
+ /**
+ * Reports a call session event received from the {@link Connection.VideoProvider}
+ * associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param event The event.
+ * @see InCallService.VideoCall.Callback#onCallSessionEvent(int)
+ * @see Connection.VideoProvider#handleCallSessionEvent(int)
+ */
public void onCallSessionEvent(VideoProvider videoProvider, int event) {}
- public void onPeerDimensionsChanged(VideoProvider videoProvider, int width, int height) {}
-
+ /**
+ * Reports a change in the peer video dimensions received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param width The updated peer video width.
+ * @param height The updated peer video height.
+ * @see InCallService.VideoCall.Callback#onPeerDimensionsChanged(int, int)
+ * @see Connection.VideoProvider#changePeerDimensions(int, int)
+ */
+ public void onPeerDimensionsChanged(VideoProvider videoProvider, int width,
+ int height) {}
+
+ /**
+ * Reports a change in the data usage (in bytes) received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param dataUsage The updated data usage (in bytes).
+ * @see InCallService.VideoCall.Callback#onCallDataUsageChanged(long)
+ * @see Connection.VideoProvider#setCallDataUsage(long)
+ */
public void onCallDataUsageChanged(VideoProvider videoProvider, long dataUsage) {}
+ /**
+ * Reports a change in the capabilities of the current camera, received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param cameraCapabilities The changed camera capabilities.
+ * @see InCallService.VideoCall.Callback#onCameraCapabilitiesChanged(
+ * VideoProfile.CameraCapabilities)
+ * @see Connection.VideoProvider#changeCameraCapabilities(
+ * VideoProfile.CameraCapabilities)
+ */
public void onCameraCapabilitiesChanged(
VideoProvider videoProvider,
VideoProfile.CameraCapabilities cameraCapabilities) {}
+ /**
+ * Reports a change in the video quality received from the
+ * {@link Connection.VideoProvider} associated with a {@link RemoteConnection}.
+ *
+ * @param videoProvider The {@link RemoteConnection.VideoProvider} invoking this method.
+ * @param videoQuality The updated peer video quality.
+ * @see InCallService.VideoCall.Callback#onVideoQualityChanged(int)
+ * @see Connection.VideoProvider#changeVideoQuality(int)
+ */
public void onVideoQualityChanged(VideoProvider videoProvider, int videoQuality) {}
}
@@ -316,14 +399,32 @@ public final class RemoteConnection {
}
}
+ /**
+ * Registers a callback to receive commands and state changes for video calls.
+ *
+ * @param l The video call callback.
+ */
public void registerCallback(Callback l) {
mCallbacks.add(l);
}
+ /**
+ * Clears the video call callback set via {@link #registerCallback}.
+ *
+ * @param l The video call callback to clear.
+ */
public void unregisterCallback(Callback l) {
mCallbacks.remove(l);
}
+ /**
+ * Sets the camera to be used for the outgoing video for the
+ * {@link RemoteConnection.VideoProvider}.
+ *
+ * @param cameraId The id of the camera (use ids as reported by
+ * {@link CameraManager#getCameraIdList()}).
+ * @see Connection.VideoProvider#onSetCamera(String)
+ */
public void setCamera(String cameraId) {
try {
mVideoProviderBinder.setCamera(cameraId);
@@ -331,6 +432,13 @@ public final class RemoteConnection {
}
}
+ /**
+ * Sets the surface to be used for displaying a preview of what the user's camera is
+ * currently capturing for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param surface The {@link Surface}.
+ * @see Connection.VideoProvider#onSetPreviewSurface(Surface)
+ */
public void setPreviewSurface(Surface surface) {
try {
mVideoProviderBinder.setPreviewSurface(surface);
@@ -338,6 +446,13 @@ public final class RemoteConnection {
}
}
+ /**
+ * Sets the surface to be used for displaying the video received from the remote device for
+ * the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param surface The {@link Surface}.
+ * @see Connection.VideoProvider#onSetDisplaySurface(Surface)
+ */
public void setDisplaySurface(Surface surface) {
try {
mVideoProviderBinder.setDisplaySurface(surface);
@@ -345,6 +460,13 @@ public final class RemoteConnection {
}
}
+ /**
+ * Sets the device orientation, in degrees, for the {@link RemoteConnection.VideoProvider}.
+ * Assumes that a standard portrait orientation of the device is 0 degrees.
+ *
+ * @param rotation The device orientation, in degrees.
+ * @see Connection.VideoProvider#onSetDeviceOrientation(int)
+ */
public void setDeviceOrientation(int rotation) {
try {
mVideoProviderBinder.setDeviceOrientation(rotation);
@@ -352,6 +474,12 @@ public final class RemoteConnection {
}
}
+ /**
+ * Sets camera zoom ratio for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param value The camera zoom ratio.
+ * @see Connection.VideoProvider#onSetZoom(float)
+ */
public void setZoom(float value) {
try {
mVideoProviderBinder.setZoom(value);
@@ -359,6 +487,14 @@ public final class RemoteConnection {
}
}
+ /**
+ * Issues a request to modify the properties of the current video session for the
+ * {@link RemoteConnection.VideoProvider}.
+ *
+ * @param fromProfile The video profile prior to the request.
+ * @param toProfile The video profile with the requested changes made.
+ * @see Connection.VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile)
+ */
public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
try {
mVideoProviderBinder.sendSessionModifyRequest(fromProfile, toProfile);
@@ -366,6 +502,13 @@ public final class RemoteConnection {
}
}
+ /**
+ * Provides a response to a request to change the current call video session
+ * properties for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @param responseProfile The response call video properties.
+ * @see Connection.VideoProvider#onSendSessionModifyResponse(VideoProfile)
+ */
public void sendSessionModifyResponse(VideoProfile responseProfile) {
try {
mVideoProviderBinder.sendSessionModifyResponse(responseProfile);
@@ -373,6 +516,12 @@ public final class RemoteConnection {
}
}
+ /**
+ * Issues a request to retrieve the capabilities of the current camera for the
+ * {@link RemoteConnection.VideoProvider}.
+ *
+ * @see Connection.VideoProvider#onRequestCameraCapabilities()
+ */
public void requestCameraCapabilities() {
try {
mVideoProviderBinder.requestCameraCapabilities();
@@ -380,6 +529,12 @@ public final class RemoteConnection {
}
}
+ /**
+ * Issues a request to retrieve the data usage (in bytes) of the video portion of the
+ * {@link RemoteConnection} for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @see Connection.VideoProvider#onRequestConnectionDataUsage()
+ */
public void requestCallDataUsage() {
try {
mVideoProviderBinder.requestCallDataUsage();
@@ -387,6 +542,12 @@ public final class RemoteConnection {
}
}
+ /**
+ * Sets the {@link Uri} of an image to be displayed to the peer device when the video signal
+ * is paused, for the {@link RemoteConnection.VideoProvider}.
+ *
+ * @see Connection.VideoProvider#onSetPauseImage(Uri)
+ */
public void setPauseImage(Uri uri) {
try {
mVideoProviderBinder.setPauseImage(uri);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index fb0ecb019951..368e1375a648 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -162,8 +162,8 @@ public class TelecomManager {
* The extra used by a {@link ConnectionService} to provide the handle of the caller that
* has initiated a new incoming call.
*/
- public static final String EXTRA_INCOMING_CALL_HANDLE =
- "android.telecom.extra.INCOMING_CALL_HANDLE";
+ public static final String EXTRA_INCOMING_CALL_ADDRESS =
+ "android.telecom.extra.INCOMING_CALL_ADDRESS";
/**
* Optional extra for {@link #ACTION_INCOMING_CALL} containing a {@link Bundle} which contains
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index 11a497678255..dabf706bc976 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -49,7 +49,32 @@ public class VideoProfile implements Parcelable {
public static final int QUALITY_DEFAULT = 4;
/**
- * Call is currently in an audio-only mode with no video transmission or receipt.
+ * Used when answering or dialing a call to indicate that the call does not have a video
+ * component.
+ * <p>
+ * Should <b>not</b> be used in comparison checks to determine if a video state represents an
+ * audio-only call.
+ * <p>
+ * The following, for example, is not the correct way to check if a call is audio-only:
+ * <pre>
+ * {@code
+ * // This is the incorrect way to check for an audio-only call.
+ * if (videoState == VideoProfile.STATE_AUDIO_ONLY) {
+ * // Handle audio-only call.
+ * }
+ * }
+ * </pre>
+ * <p>
+ * Instead, use the {@link VideoProfile#isAudioOnly(int)} helper function to check if a
+ * video state represents an audio-only call:
+ * <pre>
+ * {@code
+ * // This is the correct way to check for an audio-only call.
+ * if (VideoProfile.isAudioOnly(videoState)) {
+ * // Handle audio-only call.
+ * }
+ * }
+ * </pre>
*/
public static final int STATE_AUDIO_ONLY = 0x0;
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index d19228896c5c..16472c814fc6 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -120,8 +120,7 @@ public class PhoneStateListener {
/**
* Listen for changes to the device call state.
* {@more}
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
- * READ_PHONE_STATE}
+ *
* @see #onCallStateChanged
*/
public static final int LISTEN_CALL_STATE = 0x00000020;
@@ -137,8 +136,6 @@ public class PhoneStateListener {
* Listen for changes to the direction of data traffic on the data
* connection (cellular).
* {@more}
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
- * READ_PHONE_STATE}
* Example: The status bar uses this to display the appropriate
* data-traffic icon.
*
@@ -388,6 +385,10 @@ public class PhoneStateListener {
/**
* Callback invoked when device call state changes.
+ * @param state call state
+ * @param incomingNumber incoming call phone number. If application does not have
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} permission, an empty
+ * string will be passed as an argument.
*
* @see TelephonyManager#CALL_STATE_IDLE
* @see TelephonyManager#CALL_STATE_RINGING
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 1c69794a3d5f..b0b95f68a1c6 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -331,7 +331,7 @@ public class ImsCallProfile implements Parcelable {
videostate = VideoProfile.STATE_AUDIO_ONLY;
break;
}
- if (callProfile.isVideoPaused() && videostate != VideoProfile.STATE_AUDIO_ONLY) {
+ if (callProfile.isVideoPaused() && !VideoProfile.isAudioOnly(videostate)) {
videostate |= VideoProfile.STATE_PAUSED;
} else {
videostate &= ~VideoProfile.STATE_PAUSED;
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index dae1ac3beb80..3090a119657a 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -23,6 +23,7 @@ import android.app.AssistStructure;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Bundle;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
@@ -79,6 +80,9 @@ public class MainInteractionSession extends VoiceInteractionSession
super.onShow(args, showFlags);
mState = STATE_IDLE;
mStartIntent = args.getParcelable("intent");
+ if (mStartIntent == null) {
+ mStartIntent = new Intent(getContext(), TestInteractionActivity.class);
+ }
if (mAssistVisualizer != null) {
mAssistVisualizer.clearAssistData();
}
@@ -119,6 +123,7 @@ public class MainInteractionSession extends VoiceInteractionSession
}
public void onHandleAssist(Bundle assistBundle) {
+ boolean hasStructure = false;
if (assistBundle != null) {
Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
if (assistContext != null) {
@@ -126,6 +131,7 @@ public class MainInteractionSession extends VoiceInteractionSession
if (mAssistStructure != null) {
if (mAssistVisualizer != null) {
mAssistVisualizer.setAssistStructure(mAssistStructure);
+ hasStructure = true;
}
}
AssistContent content = AssistContent.getAssistContent(assistContext);
@@ -133,10 +139,13 @@ public class MainInteractionSession extends VoiceInteractionSession
Log.i(TAG, "Assist intent: " + content.getIntent());
Log.i(TAG, "Assist clipdata: " + content.getClipData());
}
- return;
+ }
+ Uri referrer = assistBundle.getParcelable(Intent.EXTRA_REFERRER);
+ if (referrer != null) {
+ Log.i(TAG, "Referrer: " + referrer);
}
}
- if (mAssistVisualizer != null) {
+ if (!hasStructure && mAssistVisualizer != null) {
mAssistVisualizer.clearAssistData();
}
}
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index 67db289e7afc..c038414daf8c 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -23,15 +23,18 @@ import android.content.Intent;
import android.os.Bundle;
import android.service.voice.VoiceInteractionService;
import android.util.Log;
-import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
public class TestInteractionActivity extends Activity implements View.OnClickListener {
static final String TAG = "TestInteractionActivity";
+ static final String REQUEST_ABORT = "abort";
+ static final String REQUEST_COMPLETE = "complete";
+ static final String REQUEST_PICK = "pick";
+ static final String REQUEST_CONFIRM = "confirm";
+
VoiceInteractor mInteractor;
VoiceInteractor.Request mCurrentRequest = null;
TextView mLog;
@@ -72,21 +75,32 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
mCancelButton.setOnClickListener(this);
mInteractor = getVoiceInteractor();
- mCurrentRequest = new VoiceInteractor.ConfirmationRequest(
- new VoiceInteractor.Prompt("This is a confirmation"), null) {
- @Override
- public void onCancel() {
- Log.i(TAG, "Canceled!");
- getActivity().finish();
- }
-
- @Override
- public void onConfirmationResult(boolean confirmed, Bundle result) {
- Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
- getActivity().finish();
- }
- };
- mInteractor.submitRequest(mCurrentRequest);
+
+ VoiceInteractor.Request[] active = mInteractor.getActiveRequests();
+ for (int i=0; i<active.length; i++) {
+ Log.i(TAG, "Active #" + i + " / " + active[i].getName() + ": " + active[i]);
+ }
+
+ mCurrentRequest = mInteractor.getActiveRequest(REQUEST_CONFIRM);
+ if (mCurrentRequest == null) {
+ mCurrentRequest = new VoiceInteractor.ConfirmationRequest(
+ new VoiceInteractor.Prompt("This is a confirmation"), null) {
+ @Override
+ public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ getActivity().finish();
+ }
+
+ @Override
+ public void onConfirmationResult(boolean confirmed, Bundle result) {
+ Log.i(TAG, "Confirmation result: confirmed=" + confirmed + " result=" + result);
+ getActivity().finish();
+ }
+ };
+ mInteractor.submitRequest(mCurrentRequest, REQUEST_CONFIRM);
+ } else {
+ Log.i(TAG, "Restarting with active confirmation: " + mCurrentRequest);
+ }
}
@Override
@@ -112,7 +126,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
getActivity().finish();
}
};
- mInteractor.submitRequest(req);
+ mInteractor.submitRequest(req, REQUEST_ABORT);
} else if (v == mCompleteButton) {
VoiceInteractor.CompleteVoiceRequest req = new VoiceInteractor.CompleteVoiceRequest(
new VoiceInteractor.Prompt("Woohoo, completed!"), null) {
@@ -129,7 +143,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
getActivity().finish();
}
};
- mInteractor.submitRequest(req);
+ mInteractor.submitRequest(req, REQUEST_COMPLETE);
} else if (v == mPickButton) {
VoiceInteractor.PickOptionRequest.Option[] options =
new VoiceInteractor.PickOptionRequest.Option[5];
@@ -168,7 +182,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
}
}
};
- mInteractor.submitRequest(req);
+ mInteractor.submitRequest(req, REQUEST_PICK);
} else if (v == mJumpOutButton) {
Log.i(TAG, "Jump out");
Intent intent = new Intent(Intent.ACTION_MAIN);
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
index 5d212a4afd75..a7636c32511a 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/VoiceInteractionMain.java
@@ -18,6 +18,7 @@ package com.android.test.voiceinteraction;
import android.app.Activity;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -41,6 +42,11 @@ public class VoiceInteractionMain extends Activity {
super.onDestroy();
}
+ @Override
+ public Uri onProvideReferrer() {
+ return Uri.parse("http://www.example.com/VoiceInteractionMain");
+ }
+
View.OnClickListener mStartListener = new View.OnClickListener() {
public void onClick(View v) {
startService(new Intent(VoiceInteractionMain.this, MainInteractionService.class));
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index d311cd9f0a89..10f81502f268 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -40,6 +40,7 @@ sources := \
ManifestParser.cpp \
ManifestValidator.cpp \
Png.cpp \
+ ProguardRules.cpp \
ResChunkPullParser.cpp \
Resource.cpp \
ResourceParser.cpp \
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index de2dafce3352..41c229d535e1 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -28,6 +28,7 @@
#include "ManifestValidator.h"
#include "NameMangler.h"
#include "Png.h"
+#include "ProguardRules.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceTableResolver.h"
@@ -300,6 +301,9 @@ struct AaptOptions {
// Directory to in which to generate R.java.
Maybe<Source> generateJavaClass;
+ // File in which to produce proguard rules.
+ Maybe<Source> generateProguardRules;
+
// Whether to output verbose details about
// compilation.
bool verbose = false;
@@ -417,7 +421,8 @@ bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>&
bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
- const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
+ const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
+ proguard::KeepSet* keepSet) {
SourceLogger logger(item.source);
std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
if (!root) {
@@ -435,6 +440,10 @@ bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t
xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
}
+ if (options.generateProguardRules) {
+ proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
+ }
+
BigBuffer outBuffer(1024);
Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
item.originalPackage, resolver,
@@ -509,7 +518,7 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA
bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
- const android::ResTable& table, ZipFile* outApk) {
+ const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
if (options.verbose) {
Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
}
@@ -557,6 +566,11 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver
}
}
+ if (options.generateProguardRules) {
+ proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
+ keepSet);
+ }
+
BigBuffer outBuffer(1024);
if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
resolver, {}, &outBuffer)) {
@@ -805,8 +819,10 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
return false;
}
+ proguard::KeepSet keepSet;
+
android::ResTable binTable;
- if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) {
+ if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
return false;
}
@@ -826,7 +842,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
assert(uncompressedData);
if (!linkXml(options, outTable, resolver, item, uncompressedData,
- entry->getUncompressedLen(), &outApk, &linkQueue)) {
+ entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
<< std::endl;
return false;
@@ -883,6 +899,26 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
}
}
+ // Generate the Proguard rules file.
+ if (options.generateProguardRules) {
+ const Source& outPath = options.generateProguardRules.value();
+
+ if (options.verbose) {
+ Logger::note(outPath) << "writing proguard rules." << std::endl;
+ }
+
+ std::ofstream fout(outPath.path);
+ if (!fout) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (!proguard::writeKeepSet(&fout, keepSet)) {
+ Logger::error(outPath) << "failed to write proguard rules." << std::endl;
+ return false;
+ }
+ }
+
outTable->getValueStringPool().prune();
outTable->getValueStringPool().sort(
[](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
@@ -1072,6 +1108,11 @@ static AaptOptions prepareArgs(int argc, char** argv) {
options.generateJavaClass = Source{ arg.toString() };
});
+ flag::optionalFlag("--proguard", "file in which to output proguard rules",
+ [&options](const StringPiece& arg) {
+ options.generateProguardRules = Source{ arg.toString() };
+ });
+
flag::optionalSwitch("--static-lib", "generate a static Android library", true,
&isStaticLib);
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp
new file mode 100644
index 000000000000..e89fb7c8bd3d
--- /dev/null
+++ b/tools/aapt2/ProguardRules.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ProguardRules.h"
+#include "Util.h"
+#include "XmlDom.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+class BaseVisitor : public xml::Visitor {
+public:
+ BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
+ }
+
+ virtual void visit(xml::Text*) override {};
+
+ virtual void visit(xml::Namespace* node) override {
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+ }
+
+ virtual void visit(xml::Element* node) override {
+ if (!node->namespaceUri.empty()) {
+ Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ node->namespaceUri);
+ if (maybePackage) {
+ // This is a custom view, let's figure out the class name from this.
+ std::u16string package = maybePackage.value() + u"." + node->name;
+ if (util::isJavaClassName(package)) {
+ addClass(node->lineNumber, package);
+ }
+ }
+ } else if (util::isJavaClassName(node->name)) {
+ addClass(node->lineNumber, node->name);
+ }
+
+ for (const auto& child: node->children) {
+ child->accept(this);
+ }
+ }
+
+protected:
+ void addClass(size_t lineNumber, const std::u16string& className) {
+ mKeepSet->addClass(mSource.line(lineNumber), className);
+ }
+
+ void addMethod(size_t lineNumber, const std::u16string& methodName) {
+ mKeepSet->addMethod(mSource.line(lineNumber), methodName);
+ }
+
+private:
+ Source mSource;
+ KeepSet* mKeepSet;
+};
+
+struct LayoutVisitor : public BaseVisitor {
+ LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkClass = false;
+ bool checkName = false;
+ if (node->namespaceUri.empty()) {
+ checkClass = node->name == u"view" || node->name == u"fragment";
+ } else if (node->namespaceUri == kSchemaAndroid) {
+ checkName = node->name == u"fragment";
+ }
+
+ for (const auto& attr : node->attributes) {
+ if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ addMethod(node->lineNumber, attr.value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct XmlResourceVisitor : public BaseVisitor {
+ XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkFragment = false;
+ if (node->namespaceUri.empty()) {
+ checkFragment = node->name == u"PreferenceScreen" || node->name == u"header";
+ }
+
+ if (checkFragment) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct TransitionVisitor : public BaseVisitor {
+ TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkClass = node->namespaceUri.empty() &&
+ (node->name == u"transition" || node->name == u"pathMotion");
+ if (checkClass) {
+ xml::Attribute* attr = node->findAttribute({}, u"class");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct ManifestVisitor : public BaseVisitor {
+ ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ if (node->namespaceUri.empty()) {
+ bool getName = false;
+ if (node->name == u"manifest") {
+ xml::Attribute* attr = node->findAttribute({}, u"package");
+ if (attr) {
+ mPackage = attr->value;
+ }
+ } else if (node->name == u"application") {
+ getName = true;
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ } else if (node->name == u"activity" || node->name == u"service" ||
+ node->name == u"receiver" || node->name == u"provider" ||
+ node->name == u"instrumentation") {
+ getName = true;
+ }
+
+ if (getName) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ }
+ }
+ BaseVisitor::visit(node);
+ }
+
+ std::u16string mPackage;
+};
+
+bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
+ ManifestVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ return true;
+}
+
+bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
+ KeepSet* keepSet) {
+ switch (type) {
+ case ResourceType::kLayout: {
+ LayoutVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kXml: {
+ XmlResourceVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kTransition: {
+ TransitionVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ default:
+ break;
+ }
+ return true;
+}
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
+ for (const auto& entry : keepSet.mKeepSet) {
+ for (const SourceLine& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ }
+
+ for (const auto& entry : keepSet.mKeepMethodSet) {
+ for (const SourceLine& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
+ }
+ return true;
+}
+
+} // namespace proguard
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h
new file mode 100644
index 000000000000..bbb3e64e6758
--- /dev/null
+++ b/tools/aapt2/ProguardRules.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_PROGUARD_RULES_H
+#define AAPT_PROGUARD_RULES_H
+
+#include "Resource.h"
+#include "Source.h"
+#include "XmlDom.h"
+
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+class KeepSet {
+public:
+ inline void addClass(const SourceLine& source, const std::u16string& className) {
+ mKeepSet[className].insert(source);
+ }
+
+ inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
+ mKeepMethodSet[methodName].insert(source);
+ }
+
+private:
+ friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+ std::map<std::u16string, std::set<SourceLine>> mKeepSet;
+ std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
+};
+
+bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
+bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
+ KeepSet* keepSet);
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+} // namespace proguard
+} // namespace aapt
+
+#endif // AAPT_PROGUARD_RULES_H
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 10c75aa1d45e..3606488591ba 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -19,6 +19,7 @@
#include <ostream>
#include <string>
+#include <tuple>
namespace aapt {
@@ -80,6 +81,10 @@ inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& s
return out << source.path << ":" << source.line << ":" << source.column;
}
+inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
+ return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
+}
+
} // namespace aapt
#endif // AAPT_SOURCE_H
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp
index 7adaf1ef6a92..03ecd1aca310 100644
--- a/tools/aapt2/Util.cpp
+++ b/tools/aapt2/Util.cpp
@@ -102,6 +102,51 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16
return endIter;
}
+bool isJavaClassName(const StringPiece16& str) {
+ size_t pieces = 0;
+ for (const StringPiece16& piece : tokenize(str, u'.')) {
+ pieces++;
+ if (piece.empty()) {
+ return false;
+ }
+
+ // Can't have starting or trailing $ character.
+ if (piece.data()[0] == u'$' || piece.data()[piece.size() - 1] == u'$') {
+ return false;
+ }
+
+ if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) {
+ return false;
+ }
+ }
+ return pieces >= 2;
+}
+
+Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
+ const StringPiece16& className) {
+ if (className.empty()) {
+ return {};
+ }
+
+ if (util::isJavaClassName(className)) {
+ return className.toString();
+ }
+
+ if (package.empty()) {
+ return {};
+ }
+
+ std::u16string result(package.data(), package.size());
+ if (className.data()[0] != u'.') {
+ result += u'.';
+ }
+ result.append(className.data(), className.size());
+ if (!isJavaClassName(result)) {
+ return {};
+ }
+ return result;
+}
+
static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
char16_t code = 0;
for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h
index 6015d825ccab..9cdb152bf41f 100644
--- a/tools/aapt2/Util.h
+++ b/tools/aapt2/Util.h
@@ -78,6 +78,23 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16
const StringPiece16& allowedChars);
/**
+ * Tests that the string is a valid Java class name.
+ */
+bool isJavaClassName(const StringPiece16& str);
+
+/**
+ * Converts the class name to a fully qualified class name from the given `package`. Ex:
+ *
+ * asdf --> package.asdf
+ * .asdf --> package.asdf
+ * .a.b --> package.a.b
+ * asdf.adsf --> asdf.adsf
+ */
+Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
+ const StringPiece16& className);
+
+
+/**
* Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
* This will be present in C++14 and can be removed then.
*/
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
index c16f6bb95d96..0b08d240cad3 100644
--- a/tools/aapt2/Util_test.cpp
+++ b/tools/aapt2/Util_test.cpp
@@ -93,4 +93,44 @@ TEST(UtilTest, TokenizeInput) {
ASSERT_EQ(tokenizer.end(), iter);
}
+TEST(UtilTest, IsJavaClassName) {
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
+ EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
+ EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
+ EXPECT_FALSE(util::isJavaClassName(u"android"));
+}
+
+TEST(UtilTest, FullyQualifiedClassName) {
+ Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".asdf");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.a.b");
+
+ res = util::getFullyQualifiedClassName(u"android", u"a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"");
+ ASSERT_FALSE(res);
+
+ res = util::getFullyQualifiedClassName(u"android", u"./Apple");
+ ASSERT_FALSE(res);
+}
+
+
} // namespace aapt
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
index c017a0dff091..8533c28c24bb 100644
--- a/tools/aapt2/data/AndroidManifest.xml
+++ b/tools/aapt2/data/AndroidManifest.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app">
- <application>
+ <application
+ android:name=".Activity">
</application>
</manifest>
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index ce5201b6a85d..3387135d4947 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -15,6 +15,7 @@ LOCAL_RESOURCE_DIR := res
LOCAL_LIBS := lib/out/package.apk
LOCAL_OUT := out
LOCAL_GEN := out/gen
+LOCAL_PROGUARD := out/proguard.rule
##
# AAPT2 custom rules.
@@ -57,7 +58,7 @@ $(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
- $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS)
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
# R.java: gen/com/android/app/R.java <- out/resources.arsc
# No action since R.java is generated when out/resources.arsc is.
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
index 77ccedb5f8cb..50a51d99ad0a 100644
--- a/tools/aapt2/data/res/layout/main.xml
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -5,11 +5,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <fragment class="android.test.sample.App$Inner" />
+
<variable name="user" type="com.android.User" />
<View xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/me"
android:layout_width="1dp"
+ android:onClick="doClick"
android:text="@{user.name}"
android:layout_height="match_parent"
app:layout_width="@support:bool/allow"