diff options
287 files changed, 6277 insertions, 1522 deletions
diff --git a/api/current.txt b/api/current.txt index 0228d49..fdcda1f 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); @@ -4469,6 +4470,7 @@ package android.app { method public boolean onHasView(); method public boolean onHasWindowAnimations(); method public void onInvalidateOptionsMenu(); + method public void onRequestPermissionsFromFragment(android.app.Fragment, java.lang.String[], int); method public boolean onShouldSaveFragmentState(android.app.Fragment); method public void onStartActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle); method public boolean onUseFragmentManagerInflaterFactory(); @@ -5489,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[]); } @@ -5553,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(); @@ -5790,7 +5796,6 @@ package android.app.admin { method public void setPermissionPolicy(android.content.ComponentName, int); method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>); - method public void setPreferredSetupActivity(android.content.ComponentName, android.content.ComponentName); method public void setProfileEnabled(android.content.ComponentName); method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); @@ -15243,6 +15248,7 @@ package android.media { field public static final int ORIENTATION_UNDEFINED = 0; // 0x0 field public static final java.lang.String TAG_APERTURE = "FNumber"; field public static final java.lang.String TAG_DATETIME = "DateTime"; + field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime"; field public static final java.lang.String TAG_FLASH = "Flash"; field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength"; @@ -15261,6 +15267,9 @@ package android.media { field public static final java.lang.String TAG_MAKE = "Make"; field public static final java.lang.String TAG_MODEL = "Model"; field public static final java.lang.String TAG_ORIENTATION = "Orientation"; + field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime"; + field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance"; field public static final int WHITEBALANCE_AUTO = 0; // 0x0 field public static final int WHITEBALANCE_MANUAL = 1; // 0x1 @@ -19677,7 +19686,8 @@ package android.nfc { public final class NfcEvent { field public final android.nfc.NfcAdapter nfcAdapter; - field public final byte peerLlcpVersion; + field public final int peerLlcpMajorVersion; + field public final int peerLlcpMinorVersion; } public final class NfcManager { @@ -22900,6 +22910,8 @@ package android.os { public static class Debug.MemoryInfo implements android.os.Parcelable { ctor public Debug.MemoryInfo(); method public int describeContents(); + method public java.lang.String getMemoryStat(java.lang.String); + method public java.util.Map<java.lang.String, java.lang.String> getMemoryStats(); method public int getTotalPrivateClean(); method public int getTotalPrivateDirty(); method public int getTotalPss(); @@ -23446,6 +23458,7 @@ package android.os { method public static final int getGidForName(java.lang.String); method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException; method public static final int getUidForName(java.lang.String); + method public static final boolean is64Bit(); method public static final void killProcess(int); method public static final int myPid(); method public static final int myTid(); @@ -30604,8 +30617,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"; @@ -39230,8 +39243,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(); @@ -39251,13 +39264,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 0d7a5b3..094e272 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); @@ -4565,6 +4566,7 @@ package android.app { method public boolean onHasView(); method public boolean onHasWindowAnimations(); method public void onInvalidateOptionsMenu(); + method public void onRequestPermissionsFromFragment(android.app.Fragment, java.lang.String[], int); method public boolean onShouldSaveFragmentState(android.app.Fragment); method public void onStartActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle); method public boolean onUseFragmentManagerInflaterFactory(); @@ -5585,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[]); } @@ -5649,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(); @@ -5900,7 +5906,6 @@ package android.app.admin { method public void setPermissionPolicy(android.content.ComponentName, int); method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>); - method public void setPreferredSetupActivity(android.content.ComponentName, android.content.ComponentName); method public void setProfileEnabled(android.content.ComponentName); method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); @@ -16484,6 +16489,7 @@ package android.media { field public static final int ORIENTATION_UNDEFINED = 0; // 0x0 field public static final java.lang.String TAG_APERTURE = "FNumber"; field public static final java.lang.String TAG_DATETIME = "DateTime"; + field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime"; field public static final java.lang.String TAG_FLASH = "Flash"; field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength"; @@ -16502,6 +16508,9 @@ package android.media { field public static final java.lang.String TAG_MAKE = "Make"; field public static final java.lang.String TAG_MODEL = "Model"; field public static final java.lang.String TAG_ORIENTATION = "Orientation"; + field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime"; + field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance"; field public static final int WHITEBALANCE_AUTO = 0; // 0x0 field public static final int WHITEBALANCE_MANUAL = 1; // 0x1 @@ -21595,7 +21604,8 @@ package android.nfc { public final class NfcEvent { field public final android.nfc.NfcAdapter nfcAdapter; - field public final byte peerLlcpVersion; + field public final int peerLlcpMajorVersion; + field public final int peerLlcpMinorVersion; } public final class NfcManager { @@ -24818,6 +24828,8 @@ package android.os { public static class Debug.MemoryInfo implements android.os.Parcelable { ctor public Debug.MemoryInfo(); method public int describeContents(); + method public java.lang.String getMemoryStat(java.lang.String); + method public java.util.Map<java.lang.String, java.lang.String> getMemoryStats(); method public int getTotalPrivateClean(); method public int getTotalPrivateDirty(); method public int getTotalPss(); @@ -25372,6 +25384,7 @@ package android.os { method public static final int getGidForName(java.lang.String); method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException; method public static final int getUidForName(java.lang.String); + method public static final boolean is64Bit(); method public static final void killProcess(int); method public static final int myPid(); method public static final int myTid(); @@ -32818,8 +32831,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"; @@ -41575,8 +41588,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(); @@ -41596,13 +41609,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/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index 1599459..1faf41b 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -1037,6 +1037,14 @@ public final class Pm { params.abiOverride = checkAbiArgument(nextOptionData()); } else if (opt.equals("--user")) { userId = Integer.parseInt(nextOptionData()); + } else if (opt.equals("--install-location")) { + params.installLocation = Integer.parseInt(nextOptionData()); + } else if (opt.equals("--force-uuid")) { + params.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID; + params.volumeUuid = nextOptionData(); + if ("internal".equals(params.volumeUuid)) { + params.volumeUuid = null; + } } else { throw new IllegalArgumentException("Unknown option " + opt); } @@ -1885,6 +1893,8 @@ public final class Pm { System.err.println(" pm dump PACKAGE"); System.err.println(" pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]"); System.err.println(" pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]"); + System.err.println(" [--install-location 0/1/2]"); + System.err.println(" [--force-uuid internal|UUID]"); System.err.println(" pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]"); System.err.println(" pm install-commit SESSION_ID"); System.err.println(" pm install-abandon SESSION_ID"); diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 435d5ab..d8d2737 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -441,8 +441,12 @@ public class AnimatorInflater { long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0); - int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_FLOAT); + int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_UNDEFINED); + if (valueType == VALUE_TYPE_UNDEFINED) { + valueType = inferValueTypeFromValues(arrayAnimator, R.styleable.Animator_valueFrom, + R.styleable.Animator_valueTo); + } PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType, R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, ""); if (pvh != null) { @@ -520,8 +524,14 @@ public class AnimatorInflater { ObjectAnimator oa = (ObjectAnimator) anim; String pathData = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_pathData); - // Note that if there is a pathData defined in the Object Animator, - // valueFrom / valueTo will be ignored. + // Path can be involved in an ObjectAnimator in the following 3 ways: + // 1) Path morphing: the property to be animated is pathData, and valueFrom and valueTo + // are both of pathType. valueType = pathType needs to be explicitly defined. + // 2) A property in X or Y dimension can be animated along a path: the property needs to be + // defined in propertyXName or propertyYName attribute, the path will be defined in the + // pathData attribute. valueFrom and valueTo will not be necessary for this animation. + // 3) PathInterpolator can also define a path (in pathData) for its interpolation curve. + // Here we are dealing with case 2: if (pathData != null) { String propertyXName = arrayObjectAnimator.getString(R.styleable.PropertyAnimator_propertyXName); @@ -805,6 +815,25 @@ public class AnimatorInflater { return valueType; } + private static int inferValueTypeFromValues(TypedArray styledAttributes, int valueFromId, + int valueToId) { + TypedValue tvFrom = styledAttributes.peekValue(valueFromId); + boolean hasFrom = (tvFrom != null); + int fromType = hasFrom ? tvFrom.type : 0; + TypedValue tvTo = styledAttributes.peekValue(valueToId); + boolean hasTo = (tvTo != null); + int toType = hasTo ? tvTo.type : 0; + + int valueType; + // Check whether it's color type. If not, fall back to default type (i.e. float type) + if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) { + valueType = VALUE_TYPE_COLOR; + } else { + valueType = VALUE_TYPE_FLOAT; + } + return valueType; + } + private static void dumpKeyframes(Object[] keyframes, String header) { if (keyframes == null || keyframes.length == 0) { return; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 90567c7..1b4ee2e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -690,6 +690,8 @@ public class Activity extends ContextThemeWrapper private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_"; + private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:"; + private static class ManagedDialog { Dialog mDialog; Bundle mArgs; @@ -3751,7 +3753,7 @@ public class Activity extends ContextThemeWrapper */ public final void requestPermissions(@NonNull String[] permissions, int requestCode) { Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); - startActivityForResult(intent, requestCode); + startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null); } /** @@ -4279,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() @@ -4463,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, @@ -4616,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 @@ -6330,32 +6350,32 @@ public class Activity extends ContextThemeWrapper + ", resCode=" + resultCode + ", data=" + data); mFragments.noteStateNotSaved(); if (who == null) { - if (isRequestPermissionResult(data)) { + onActivityResult(requestCode, resultCode, data); + } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) { + who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length()); + if (TextUtils.isEmpty(who)) { dispatchRequestPermissionsResult(requestCode, data); } else { - onActivityResult(requestCode, resultCode, data); - } - } else { - if (who.startsWith("@android:view:")) { - ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( - getActivityToken()); - for (ViewRootImpl viewRoot : views) { - if (viewRoot.getView() != null - && viewRoot.getView().dispatchActivityResult( - who, requestCode, resultCode, data)) { - return; - } - } - } else { Fragment frag = mFragments.findFragmentByWho(who); if (frag != null) { - if (isRequestPermissionResult(data)) { - dispatchRequestPermissionsResultToFragment(requestCode, data, frag); - } else { - frag.onActivityResult(requestCode, resultCode, data); - } + dispatchRequestPermissionsResultToFragment(requestCode, data, frag); } } + } else if (who.startsWith("@android:view:")) { + ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews( + getActivityToken()); + for (ViewRootImpl viewRoot : views) { + if (viewRoot.getView() != null + && viewRoot.getView().dispatchActivityResult( + who, requestCode, resultCode, data)) { + return; + } + } + } else { + Fragment frag = mFragments.findFragmentByWho(who); + if (frag != null) { + frag.onActivityResult(requestCode, resultCode, data); + } } } @@ -6466,11 +6486,6 @@ public class Activity extends ContextThemeWrapper fragement.onRequestPermissionsResult(requestCode, permissions, grantResults); } - private static boolean isRequestPermissionResult(Intent intent) { - return intent != null - && PackageManager.ACTION_REQUEST_PERMISSIONS.equals(intent.getAction()); - } - class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); @@ -6518,6 +6533,14 @@ public class Activity extends ContextThemeWrapper } @Override + public void onRequestPermissionsFromFragment(Fragment fragment, String[] permissions, + int requestCode) { + String who = REQUEST_PERMISSIONS_WHO_PREFIX + fragment.mWho; + Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); + startActivityForResult(who, intent, requestCode, null); + } + + @Override public boolean onHasWindowAnimations() { return getWindow() != null; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 3892dd9..da345a6 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 bb553e4..6ae21eb 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; } @@ -2534,15 +2535,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - ComponentName preferredActivity = ComponentName.readFromParcel(data); - int userId = data.readInt(); - updatePreferredSetupActivity(preferredActivity, userId); - reply.writeNoException(); - return true; - } - case GET_PACKAGE_PROCESS_STATE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String pkg = data.readString(); @@ -5376,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); @@ -5384,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(); @@ -5868,20 +5866,6 @@ class ActivityManagerProxy implements IActivityManager } @Override - public void updatePreferredSetupActivity(ComponentName preferredActivity, int userId) - throws RemoteException { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - ComponentName.writeToParcel(preferredActivity, data); - data.writeInt(userId); - mRemote.transact(UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION, data, reply, 0); - reply.readException(); - data.recycle(); - reply.recycle(); - } - - @Override public int getPackageProcessState(String packageName) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ffb3fb8..e21c04a 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 d23e73d..173b237 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 829b098..be36af7 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/Fragment.java b/core/java/android/app/Fragment.java index 26d4fd4..95b3b8e 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1200,9 +1200,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - Intent intent = - mHost.getContext().getPackageManager().buildRequestPermissionsIntent(permissions); - mHost.onStartActivityFromFragment(this, intent, requestCode, null); + mHost.onRequestPermissionsFromFragment(this, permissions,requestCode); } /** diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java index 3e753f0..7b01307 100644 --- a/core/java/android/app/FragmentHostCallback.java +++ b/core/java/android/app/FragmentHostCallback.java @@ -16,8 +16,8 @@ package android.app; +import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -126,6 +126,14 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer { } /** + * Requests permissions from the given fragment. + * See {@link Activity#requestPermissions(String[], int)} + */ + public void onRequestPermissionsFromFragment(@NonNull Fragment fragment, + @NonNull String[] permissions, int requestCode) { + } + + /** * Return {@code true} if there are window animations. */ public boolean onHasWindowAnimations() { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index e87eabe..9311e5e 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; @@ -502,8 +502,6 @@ public interface IActivityManager extends IInterface { throws RemoteException; public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException; public void updateDeviceOwner(String packageName) throws RemoteException; - public void updatePreferredSetupActivity(ComponentName preferredActivity, int userId) - throws RemoteException; public int getPackageProcessState(String packageName) throws RemoteException; @@ -850,8 +848,7 @@ public interface IActivityManager extends IInterface { int GET_PACKAGE_PROCESS_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+293; int SHOW_LOCK_TASK_ESCAPE_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+294; int UPDATE_DEVICE_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+295; - int UPDATE_PREFERRED_SETUP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+296; - int KEYGUARD_GOING_AWAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+297; - int REGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+298; - int UNREGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+299; + int KEYGUARD_GOING_AWAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+296; + int REGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+297; + int UNREGISTER_UID_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+298; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 0cc57ba..653f1b6 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 >= 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 eccd9dc..9cc399d 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/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 55eaf27..3ab0e01 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4404,24 +4404,6 @@ public class DevicePolicyManager { } /** - * Called by a device initializer to set the activity to be launched on device boot or after a - * user switch during user setup. This activity will be started regardless of the priority of - * other 'home' activities. Once user setup is complete, the preferred setup activity will be - * ignored. - * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param activity The Activity to be started by default during user setup. - */ - public void setPreferredSetupActivity(@NonNull ComponentName admin, - @NonNull ComponentName activity) { - try { - mService.setPreferredSetupActivity(admin, activity); - } catch (RemoteException re) { - Log.w(TAG, "Failed talking with device policy service", re); - } - } - - /** * Called by profile or device owners to set the default response for future runtime permission * requests by applications. The policy can allow for normal operation which prompts the * user to grant a permission, or can allow automatic granting or denying of runtime diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 477a338..8c7b20a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -218,8 +218,6 @@ interface IDevicePolicyManager { String getDeviceInitializer(); ComponentName getDeviceInitializerComponent(); - void setPreferredSetupActivity(in ComponentName admin, in ComponentName activity); - void setUserIcon(in ComponentName admin, in Bitmap icon); void sendDeviceInitializerStatus(int statusCode, String description); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index ab3f7bc..97afafa 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 dcf06d8..c96fe71 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 a80f55c..c15852d 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 6ca6976..6302521 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 5c23204..25be96a 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/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c016ac3..68092c8 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -401,6 +401,9 @@ public abstract class PackageManager { */ public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100; + /** {@hide} */ + public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200; + /** * Flag parameter for * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 755eb5b..83b0140 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -3357,6 +3357,7 @@ public class PackageParser { info.labelRes = target.info.labelRes; info.nonLocalizedLabel = target.info.nonLocalizedLabel; info.launchMode = target.info.launchMode; + info.lockTaskLaunchMode = target.info.lockTaskLaunchMode; info.processName = target.info.processName; if (info.descriptionRes == 0) { info.descriptionRes = target.info.descriptionRes; diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index e61813c..82d3e0a 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/nfc/NfcEvent.java b/core/java/android/nfc/NfcEvent.java index cf1d71a..aff4f52 100644 --- a/core/java/android/nfc/NfcEvent.java +++ b/core/java/android/nfc/NfcEvent.java @@ -39,13 +39,18 @@ public final class NfcEvent { public final NfcAdapter nfcAdapter; /** - * The LLCP version of the peer associated with the NFC event. - * The major version is in the top nibble, the minor version is in the bottom nibble. + * The major LLCP version number of the peer associated with the NFC event. */ - public final byte peerLlcpVersion; + public final int peerLlcpMajorVersion; + + /** + * The minor LLCP version number of the peer associated with the NFC event. + */ + public final int peerLlcpMinorVersion; NfcEvent(NfcAdapter nfcAdapter, byte peerLlcpVersion) { this.nfcAdapter = nfcAdapter; - this.peerLlcpVersion = peerLlcpVersion; + this.peerLlcpMajorVersion = (peerLlcpVersion & 0xF0) >> 4; + this.peerLlcpMinorVersion = peerLlcpVersion & 0x0F; } } diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 19c8fa9..87e8c5e 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -34,6 +34,7 @@ import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; import java.util.Map; import org.apache.harmony.dalvik.ddmc.Chunk; @@ -389,6 +390,132 @@ public final class Debug } } + /** + * Returns the value of a particular memory statistic or {@code null} if no + * such memory statistic exists. + * + * <p>The following table lists the memory statistics that are supported. + * Note that memory statistics may be added or removed in a future API level.</p> + * + * <table> + * <thead> + * <tr> + * <th>Memory statistic name</th> + * <th>Meaning</th> + * <th>Example</th> + * <th>Supported (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>summary.java-heap</td> + * <td>The private Java Heap usage in kB. This corresponds to the Java Heap field + * in the App Summary section output by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.native-heap</td> + * <td>The private Native Heap usage in kB. This corresponds to the Native Heap + * field in the App Summary section output by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.code</td> + * <td>The memory usage for static code and resources in kB. This corresponds to + * the Code field in the App Summary section output by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.stack</td> + * <td>The stack usage in kB. This corresponds to the Stack field in the + * App Summary section output by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.graphics</td> + * <td>The graphics usage in kB. This corresponds to the Graphics field in the + * App Summary section output by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.private-other</td> + * <td>Other private memory usage in kB. This corresponds to the Private Other + * field output in the App Summary section by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.system</td> + * <td>Shared and system memory usage in kB. This corresponds to the System + * field output in the App Summary section by dumpsys meminfo.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.total-pss</td> + * <td>Total PPS memory usage in kB.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * <tr> + * <td>summary.total-swap</td> + * <td>Total swap usage in kB.</td> + * <td>{@code 1442}</td> + * <td>23</td> + * </tr> + * </tbody> + * </table> + */ + public String getMemoryStat(String statName) { + switch(statName) { + case "summary.java-heap": + return Integer.toString(getSummaryJavaHeap()); + case "summary.native-heap": + return Integer.toString(getSummaryNativeHeap()); + case "summary.code": + return Integer.toString(getSummaryCode()); + case "summary.stack": + return Integer.toString(getSummaryStack()); + case "summary.graphics": + return Integer.toString(getSummaryGraphics()); + case "summary.private-other": + return Integer.toString(getSummaryPrivateOther()); + case "summary.system": + return Integer.toString(getSummarySystem()); + case "summary.total-pss": + return Integer.toString(getSummaryTotalPss()); + case "summary.total-swap": + return Integer.toString(getSummaryTotalSwap()); + default: + return null; + } + } + + /** + * Returns a map of the names/values of the memory statistics + * that {@link #getMemoryStat(String)} supports. + * + * @return a map of the names/values of the supported memory statistics. + */ + public Map<String, String> getMemoryStats() { + Map<String, String> stats = new HashMap<String, String>(); + stats.put("summary.java-heap", Integer.toString(getSummaryJavaHeap())); + stats.put("summary.native-heap", Integer.toString(getSummaryNativeHeap())); + stats.put("summary.code", Integer.toString(getSummaryCode())); + stats.put("summary.stack", Integer.toString(getSummaryStack())); + stats.put("summary.graphics", Integer.toString(getSummaryGraphics())); + stats.put("summary.private-other", Integer.toString(getSummaryPrivateOther())); + stats.put("summary.system", Integer.toString(getSummarySystem())); + stats.put("summary.total-pss", Integer.toString(getSummaryTotalPss())); + stats.put("summary.total-swap", Integer.toString(getSummaryTotalSwap())); + return stats; + } + /** * Pss of Java Heap bytes in KB due to the application. * Notes: diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 009649f..dbb5146 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -21,6 +21,7 @@ import android.net.LocalSocketAddress; import android.system.Os; import android.util.Log; import com.android.internal.os.Zygote; +import dalvik.system.VMRuntime; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.IOException; @@ -744,7 +745,14 @@ public class Process { * @return Returns the number of milliseconds this process has return. */ public static final native long getElapsedCpuTime(); - + + /** + * Returns true if the current process is a 64-bit runtime. + */ + public static final boolean is64Bit() { + return VMRuntime.getRuntime().is64Bit(); + } + /** * Returns the identifier of this process, which can be used with * {@link #killProcess} and {@link #sendSignal}. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cac4a53..640f434 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/LayoutDirection.java b/core/java/android/util/LayoutDirection.java index 20af20b..03077e4 100644 --- a/core/java/android/util/LayoutDirection.java +++ b/core/java/android/util/LayoutDirection.java @@ -27,6 +27,12 @@ public final class LayoutDirection { private LayoutDirection() {} /** + * An undefined layout direction. + * @hide + */ + public static final int UNDEFINED = -1; + + /** * Horizontal layout direction is from Left to Right. */ public static final int LTR = 0; diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 121485a..fe41932 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 342315b..be372d0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1872,6 +1872,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public @interface ResolvedLayoutDir {} /** + * A flag to indicate that the layout direction of this view has not been defined yet. + * @hide + */ + public static final int LAYOUT_DIRECTION_UNDEFINED = LayoutDirection.UNDEFINED; + + /** * Horizontal layout direction of this view is from Left to Right. * Use with {@link #setLayoutDirection}. */ @@ -16064,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 c0395cf..76c8fbd 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -298,6 +298,17 @@ public class EditorInfo implements InputType, Parcelable { /** * Name of the package that owns this editor. + * + * <p><strong>IME authors:</strong> In API level 22 + * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and prior, do not trust this package + * 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.</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.</p> */ public String packageName; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 5080fcc..aa72eb3 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 09afcf1..27033ad 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/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index f153ce5..9d14254 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -185,6 +185,8 @@ public class LinearLayout extends ViewGroup { private int mShowDividers; private int mDividerPadding; + private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED; + public LinearLayout(Context context) { this(context, null); } @@ -1567,6 +1569,17 @@ public class LinearLayout extends ViewGroup { } } + @Override + public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + if (layoutDirection != mLayoutDirection) { + mLayoutDirection = layoutDirection; + if (mOrientation == HORIZONTAL) { + requestLayout(); + } + } + } + /** * Position the children during a layout pass if the orientation of this * LinearLayout is set to {@link #HORIZONTAL}. diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index fe79eff..27aec4e 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 e6165a1..4290e22 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 ee7ed0b..087db78 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 3557209..1f59672 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 961b0df..da98a67 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/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index c010dfe..294e4ba 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -2741,7 +2741,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { try { mode = getCallback().onWindowStartingActionMode(wrappedCallback, type); } catch (AbstractMethodError ame) { - // Older apps might not implement this callback method. + // Older apps might not implement the typed version of this method. + if (type == ActionMode.TYPE_PRIMARY) { + try { + mode = getCallback().onWindowStartingActionMode(wrappedCallback); + } catch (AbstractMethodError ame2) { + // Older apps might not implement this callback method at all. + } + } } } if (mode != null) { diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index f75b139..8db363d 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -273,7 +273,8 @@ public class ActionMenuItemView extends TextView Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT); if (midy < displayFrame.height()) { // Show along the top; follow action buttons - cheatSheet.setGravity(Gravity.TOP | Gravity.END, referenceX, height); + cheatSheet.setGravity(Gravity.TOP | Gravity.END, referenceX, + screenPos[1] + height - displayFrame.top); } else { // Show along the bottom center cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height); diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b36acbc..506f188 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -377,6 +377,9 @@ as a result of the current profile admin gone missing. [CHAR LIMIT=NONE]--> <string name="work_profile_deleted_details">The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your administrator for assistance.</string> + <!-- Content text for a notification. The Title of the notification is "work_profile_deleted", + This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]--> + <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string> <!-- Factory reset warning dialog strings--> <skip /> <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] --> @@ -414,8 +417,14 @@ <string name="silent_mode_ring">Ringer on</string> <!-- Reboot to Recovery Progress Dialog. This is shown before it reboots to recovery. --> - <string name="reboot_to_recovery_title">Prepare for update</string> - <string name="reboot_to_recovery_progress">Processing the update package\u2026</string> + <string name="reboot_to_update_title">Android system update</string> + <string name="reboot_to_update_prepare">Preparing to update\u2026</string> + <string name="reboot_to_update_package">Processing the update package\u2026</string> + <string name="reboot_to_update_reboot">Restarting\u2026</string> + + <!-- Reboot to Recovery for factory reset. --> + <string name="reboot_to_reset_title">Factory data reset</string> + <string name="reboot_to_reset_message">Restarting\u2026</string> <!-- Shutdown Progress Dialog. This is shown if the user chooses to power off the phone. --> <string name="shutdown_progress">Shutting down\u2026</string> @@ -542,7 +551,7 @@ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_location">Location</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permgroupdesc_location">access your location</string> + <string name="permgroupdesc_location">access this device\'s location</string> <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_socialInfo">Your social information</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 909bad1..4c388b6 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -819,8 +819,12 @@ <java-symbol type="string" name="mobile_provisioning_url" /> <java-symbol type="string" name="mobile_redirected_provisioning_url" /> <java-symbol type="string" name="quick_contacts_not_available" /> - <java-symbol type="string" name="reboot_to_recovery_progress" /> - <java-symbol type="string" name="reboot_to_recovery_title" /> + <java-symbol type="string" name="reboot_to_update_package" /> + <java-symbol type="string" name="reboot_to_update_prepare" /> + <java-symbol type="string" name="reboot_to_update_title" /> + <java-symbol type="string" name="reboot_to_update_reboot" /> + <java-symbol type="string" name="reboot_to_reset_title" /> + <java-symbol type="string" name="reboot_to_reset_message" /> <java-symbol type="string" name="reboot_safemode_confirm" /> <java-symbol type="string" name="reboot_safemode_title" /> <java-symbol type="string" name="relationTypeAssistant" /> @@ -1092,6 +1096,7 @@ <java-symbol type="string" name="work_profile_deleted" /> <java-symbol type="string" name="work_profile_deleted_description" /> <java-symbol type="string" name="work_profile_deleted_details" /> + <java-symbol type="string" name="work_profile_deleted_description_dpm_wipe" /> <java-symbol type="string" name="factory_reset_warning" /> <java-symbol type="string" name="factory_reset_message" /> <java-symbol type="string" name="lockscreen_transport_play_description" /> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index 8fbd214..0d9980a 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 60da5d5..8b6de76 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() { @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() { @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> @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> @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> @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> @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> @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 ad348f8..893771a 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 e555cc0..f37cf07 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 0e8d03e..19375a2 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 4d6178f..688936c 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 c5ea0f7..69155a8 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 5643caf..d33692a 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 7c9c0cf..c03be63 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 d861302..19ff9c7 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 e3c2d1d..5af4181 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 e7529e1..0639d49 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 cbb6fd5..900a621 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 d997ef4..edfda62 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 29fbf0c..5850dc6 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/AudioRecord.java b/media/java/android/media/AudioRecord.java index 7eb1357..3cbc405 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -1277,7 +1277,8 @@ public class AudioRecord native_enableDeviceCallback(); } mRoutingChangeListeners.put( - listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + listener, new NativeRoutingEventHandlerDelegate(this, listener, + handler != null ? handler : new Handler(mInitializationLooper))); } } } diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 7293c6c..f395cb3 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -2244,7 +2244,8 @@ public class AudioTrack native_enableDeviceCallback(); } mRoutingChangeListeners.put( - listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + listener, new NativeRoutingEventHandlerDelegate(this, listener, + handler != null ? handler : new Handler(mInitializationLooper))); } } } diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index aa5d43a..6bf5721 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -57,6 +57,16 @@ public class ExifInterface { public static final String TAG_APERTURE = "FNumber"; /** Type is String. */ public static final String TAG_ISO = "ISOSpeedRatings"; + /** Type is String. */ + public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME = "SubSecTime"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; + /** Type is int. */ + public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + + /** * @hide diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index eec4960..a79dd04 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 8b2bd16..673c4ba 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 “patching” 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 “patching” 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> +<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 -“res/xml/synth_device_info.xml”.</p> +“res/xml/synth_device_info.xml”. The port names that you +declare in this file will be available from PortInfo.getName().</p> <pre class=prettyprint> <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; + @Override public void onCreate() { super.onCreate(); @@ -311,10 +328,12 @@ public class MidiSynthDeviceService extends MidiDeviceService { */ @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 93b8ec7..ce7f7e5 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 a4ed67b..6650cf9 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/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 96b72a2..fdc586b 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -92,6 +92,7 @@ static jint translateError(int code) { } } +static Mutex sLock; // ---------------------------------------------------------------------------- static void effectCallback(int event, void* user, void *info) { @@ -182,6 +183,32 @@ effectCallback_Exit: } // ---------------------------------------------------------------------------- + +static sp<AudioEffect> getAudioEffect(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + AudioEffect* const ae = + (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect); + return sp<AudioEffect>(ae); +} + +static sp<AudioEffect> setAudioEffect(JNIEnv* env, jobject thiz, + const sp<AudioEffect>& ae) +{ + Mutex::Autolock l(sLock); + sp<AudioEffect> old = + (AudioEffect*)env->GetLongField(thiz, fields.fidNativeAudioEffect); + if (ae.get()) { + ae->incStrong((void*)setAudioEffect); + } + if (old != 0) { + old->decStrong((void*)setAudioEffect); + } + env->SetLongField(thiz, fields.fidNativeAudioEffect, (jlong)ae.get()); + return old; +} + +// ---------------------------------------------------------------------------- // This function gets some field IDs, which in turn causes class initialization. // It is called from a static block in AudioEffect, which won't run until the // first time an instance of this class is used. @@ -257,7 +284,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t ALOGV("android_media_AudioEffect_native_setup"); AudioEffectJniStorage* lpJniStorage = NULL; int lStatus = AUDIOEFFECT_ERROR_NO_MEMORY; - AudioEffect* lpAudioEffect = NULL; + sp<AudioEffect> lpAudioEffect; jint* nId = NULL; const char *typeStr = NULL; const char *uuidStr = NULL; @@ -272,6 +299,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t ScopedUtfChars opPackageNameStr(env, opPackageName); + setAudioEffect(env, thiz, 0); + if (type != NULL) { typeStr = env->GetStringUTFChars(type, NULL); if (typeStr == NULL) { // Out of memory @@ -324,7 +353,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t &lpJniStorage->mCallbackData, sessionId, 0); - if (lpAudioEffect == NULL) { + if (lpAudioEffect == 0) { ALOGE("Error creating AudioEffect"); goto setup_failure; } @@ -394,7 +423,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t env->SetObjectArrayElement(javadesc, 0, jdesc); - env->SetLongField(thiz, fields.fidNativeAudioEffect, (jlong)lpAudioEffect); + setAudioEffect(env, thiz, lpAudioEffect); env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage); @@ -407,12 +436,9 @@ setup_failure: env->ReleasePrimitiveArrayCritical(jId, nId, 0); } - if (lpAudioEffect) { - delete lpAudioEffect; - } - env->SetLongField(thiz, fields.fidNativeAudioEffect, 0); - if (lpJniStorage) { + env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class); + env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref); delete lpJniStorage; } env->SetLongField(thiz, fields.fidJniData, 0); @@ -430,20 +456,20 @@ setup_failure: // ---------------------------------------------------------------------------- -static void android_media_AudioEffect_native_finalize(JNIEnv *env, jobject thiz) { - ALOGV("android_media_AudioEffect_native_finalize jobject: %p\n", thiz); - - // delete the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - if (lpAudioEffect) { - ALOGV("deleting AudioEffect: %p\n", lpAudioEffect); - delete lpAudioEffect; +static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) { + sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0); + if (lpAudioEffect == 0) { + return; } // delete the JNI data - AudioEffectJniStorage* lpJniStorage = (AudioEffectJniStorage *)env->GetLongField( - thiz, fields.fidJniData); + AudioEffectJniStorage* lpJniStorage = + (AudioEffectJniStorage *)env->GetLongField(thiz, fields.fidJniData); + + // reset the native resources in the Java object so any attempt to access + // them after a call to release fails. + env->SetLongField(thiz, fields.fidJniData, 0); + if (lpJniStorage) { ALOGV("deleting pJniStorage: %p\n", lpJniStorage); delete lpJniStorage; @@ -451,24 +477,16 @@ static void android_media_AudioEffect_native_finalize(JNIEnv *env, jobject thiz } // ---------------------------------------------------------------------------- -static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) { - - // do everything a call to finalize would - android_media_AudioEffect_native_finalize(env, thiz); - // + reset the native resources in the Java object so any attempt to access - // them after a call to release fails. - env->SetLongField(thiz, fields.fidNativeAudioEffect, 0); - env->SetLongField(thiz, fields.fidJniData, 0); +static void android_media_AudioEffect_native_finalize(JNIEnv *env, jobject thiz) { + ALOGV("android_media_AudioEffect_native_finalize jobject: %p\n", thiz); + android_media_AudioEffect_native_release(env, thiz); } static jint android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for enable()"); return AUDIOEFFECT_ERROR_NO_INIT; @@ -480,11 +498,8 @@ android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean static jboolean android_media_AudioEffect_native_getEnabled(JNIEnv *env, jobject thiz) { - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for getEnabled()"); return JNI_FALSE; @@ -501,11 +516,8 @@ android_media_AudioEffect_native_getEnabled(JNIEnv *env, jobject thiz) static jboolean android_media_AudioEffect_native_hasControl(JNIEnv *env, jobject thiz) { - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *)env->GetLongField( - thiz, fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for hasControl()"); return JNI_FALSE; @@ -528,10 +540,8 @@ static jint android_media_AudioEffect_native_setParameter(JNIEnv *env, effect_param_t *p; int voffset; - AudioEffect* lpAudioEffect = (AudioEffect *) env->GetLongField(thiz, - fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for setParameter()"); return AUDIOEFFECT_ERROR_NO_INIT; @@ -591,10 +601,8 @@ android_media_AudioEffect_native_getParameter(JNIEnv *env, effect_param_t *p; int voffset; - AudioEffect* lpAudioEffect = (AudioEffect *) env->GetLongField(thiz, - fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for getParameter()"); return AUDIOEFFECT_ERROR_NO_INIT; @@ -657,11 +665,8 @@ static jint android_media_AudioEffect_native_command(JNIEnv *env, jobject thiz, jbyte* pReplyData = NULL; jint lStatus = AUDIOEFFECT_ERROR_BAD_VALUE; - // retrieve the AudioEffect object - AudioEffect* lpAudioEffect = (AudioEffect *) env->GetLongField(thiz, - fields.fidNativeAudioEffect); - - if (lpAudioEffect == NULL) { + sp<AudioEffect> lpAudioEffect = getAudioEffect(env, thiz); + if (lpAudioEffect == 0) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioEffect pointer for setParameter()"); return AUDIOEFFECT_ERROR_NO_INIT; diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index abc681e..6098b4a 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -102,14 +102,14 @@ struct visualizer_callback_cookie { }; // ---------------------------------------------------------------------------- -class visualizerJniStorage { +class VisualizerJniStorage { public: visualizer_callback_cookie mCallbackData; - visualizerJniStorage() { + VisualizerJniStorage() { } - ~visualizerJniStorage() { + ~VisualizerJniStorage() { } }; @@ -135,6 +135,7 @@ static jint translateError(int code) { } } +static Mutex sLock; // ---------------------------------------------------------------------------- static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) { @@ -227,15 +228,30 @@ static void captureCallback(void* user, } } -static Visualizer *getVisualizer(JNIEnv* env, jobject thiz) +// ---------------------------------------------------------------------------- + +static sp<Visualizer> getVisualizer(JNIEnv* env, jobject thiz) { - Visualizer *v = (Visualizer *)env->GetLongField( - thiz, fields.fidNativeVisualizer); - if (v == NULL) { - jniThrowException(env, "java/lang/IllegalStateException", - "Unable to retrieve Visualizer pointer"); + Mutex::Autolock l(sLock); + Visualizer* const v = + (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer); + return sp<Visualizer>(v); +} + +static sp<Visualizer> setVisualizer(JNIEnv* env, jobject thiz, + const sp<Visualizer>& v) +{ + Mutex::Autolock l(sLock); + sp<Visualizer> old = + (Visualizer*)env->GetLongField(thiz, fields.fidNativeVisualizer); + if (v.get()) { + v->incStrong((void*)setVisualizer); + } + if (old != 0) { + old->decStrong((void*)setVisualizer); } - return v; + env->SetLongField(thiz, fields.fidNativeVisualizer, (jlong)v.get()); + return old; } // ---------------------------------------------------------------------------- @@ -318,7 +334,7 @@ static void android_media_visualizer_effect_callback(int32_t event, void *info) { if ((event == AudioEffect::EVENT_ERROR) && (*((status_t*)info) == DEAD_OBJECT)) { - visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user; + VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage*)user; visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData; JNIEnv *env = AndroidRuntime::getJNIEnv(); @@ -336,14 +352,16 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th jint sessionId, jintArray jId, jstring opPackageName) { ALOGV("android_media_visualizer_native_setup"); - visualizerJniStorage* lpJniStorage = NULL; + VisualizerJniStorage* lpJniStorage = NULL; int lStatus = VISUALIZER_ERROR_NO_MEMORY; - Visualizer* lpVisualizer = NULL; + sp<Visualizer> lpVisualizer; jint* nId = NULL; ScopedUtfChars opPackageNameStr(env, opPackageName); - lpJniStorage = new visualizerJniStorage(); + setVisualizer(env, thiz, 0); + + lpJniStorage = new VisualizerJniStorage(); if (lpJniStorage == NULL) { ALOGE("setup: Error creating JNI Storage"); goto setup_failure; @@ -371,7 +389,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th android_media_visualizer_effect_callback, lpJniStorage, sessionId); - if (lpVisualizer == NULL) { + if (lpVisualizer == 0) { ALOGE("Error creating Visualizer"); goto setup_failure; } @@ -392,7 +410,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th env->ReleasePrimitiveArrayCritical(jId, nId, 0); nId = NULL; - env->SetLongField(thiz, fields.fidNativeVisualizer, (jlong)lpVisualizer); + setVisualizer(env, thiz, lpVisualizer); env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage); @@ -405,12 +423,9 @@ setup_failure: env->ReleasePrimitiveArrayCritical(jId, nId, 0); } - if (lpVisualizer) { - delete lpVisualizer; - } - env->SetLongField(thiz, fields.fidNativeVisualizer, 0); - if (lpJniStorage) { + env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class); + env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref); delete lpJniStorage; } env->SetLongField(thiz, fields.fidJniData, 0); @@ -419,49 +434,44 @@ setup_failure: } // ---------------------------------------------------------------------------- -static void android_media_visualizer_native_finalize(JNIEnv *env, jobject thiz) { - ALOGV("android_media_visualizer_native_finalize jobject: %p\n", thiz); - - // delete the Visualizer object - Visualizer* lpVisualizer = (Visualizer *)env->GetLongField( - thiz, fields.fidNativeVisualizer); - if (lpVisualizer) { - ALOGV("deleting Visualizer: %p\n", lpVisualizer); - delete lpVisualizer; +static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) { + sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0); + if (lpVisualizer == 0) { + return; } // delete the JNI data - visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetLongField( - thiz, fields.fidJniData); + VisualizerJniStorage* lpJniStorage = + (VisualizerJniStorage *)env->GetLongField(thiz, fields.fidJniData); + + // reset the native resources in the Java object so any attempt to access + // them after a call to release fails. + env->SetLongField(thiz, fields.fidJniData, 0); + if (lpJniStorage) { ALOGV("deleting pJniStorage: %p\n", lpJniStorage); delete lpJniStorage; } } -// ---------------------------------------------------------------------------- -static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) { - - // do everything a call to finalize would - android_media_visualizer_native_finalize(env, thiz); - // + reset the native resources in the Java object so any attempt to access - // them after a call to release fails. - env->SetLongField(thiz, fields.fidNativeVisualizer, 0); - env->SetLongField(thiz, fields.fidJniData, 0); +static void android_media_visualizer_native_finalize(JNIEnv *env, jobject thiz) { + ALOGV("android_media_visualizer_native_finalize jobject: %p\n", thiz); + android_media_visualizer_native_release(env, thiz); } +// ---------------------------------------------------------------------------- static jint android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } jint retVal = translateError(lpVisualizer->setEnabled(enabled)); if (!enabled) { - visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetLongField( + VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage *)env->GetLongField( thiz, fields.fidJniData); if (NULL != lpJniStorage) @@ -474,8 +484,8 @@ android_media_visualizer_native_setEnabled(JNIEnv *env, jobject thiz, jboolean e static jboolean android_media_visualizer_native_getEnabled(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return JNI_FALSE; } @@ -507,8 +517,8 @@ android_media_visualizer_native_getMaxCaptureRate(JNIEnv* /* env */, jobject /* static jint android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint size) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -518,8 +528,8 @@ android_media_visualizer_native_setCaptureSize(JNIEnv *env, jobject thiz, jint s static jint android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return -1; } return (jint) lpVisualizer->getCaptureSize(); @@ -528,8 +538,8 @@ android_media_visualizer_native_getCaptureSize(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint mode) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -539,8 +549,8 @@ android_media_visualizer_native_setScalingMode(JNIEnv *env, jobject thiz, jint m static jint android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return -1; } return (jint)lpVisualizer->getScalingMode(); @@ -549,8 +559,8 @@ android_media_visualizer_native_getScalingMode(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, jint mode) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } return translateError(lpVisualizer->setMeasurementMode(mode)); @@ -559,8 +569,8 @@ android_media_visualizer_native_setMeasurementMode(JNIEnv *env, jobject thiz, ji static jint android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return MEASUREMENT_MODE_NONE; } return lpVisualizer->getMeasurementMode(); @@ -569,8 +579,8 @@ android_media_visualizer_native_getMeasurementMode(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return -1; } return (jint) lpVisualizer->getSamplingRate(); @@ -579,8 +589,8 @@ android_media_visualizer_native_getSamplingRate(JNIEnv *env, jobject thiz) static jint android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArray jWaveform) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -597,8 +607,8 @@ android_media_visualizer_native_getWaveForm(JNIEnv *env, jobject thiz, jbyteArra static jint android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFft) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } @@ -616,8 +626,8 @@ android_media_visualizer_native_getFft(JNIEnv *env, jobject thiz, jbyteArray jFf static jint android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jPeakRmsObj) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } int32_t measurements[2]; @@ -635,11 +645,11 @@ android_media_visualizer_native_getPeakRms(JNIEnv *env, jobject thiz, jobject jP static jint android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean jWaveform, jboolean jFft) { - Visualizer* lpVisualizer = getVisualizer(env, thiz); - if (lpVisualizer == NULL) { + sp<Visualizer> lpVisualizer = getVisualizer(env, thiz); + if (lpVisualizer == 0) { return VISUALIZER_ERROR_NO_INIT; } - visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetLongField(thiz, + VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage *)env->GetLongField(thiz, fields.fidJniData); if (lpJniStorage == NULL) { return VISUALIZER_ERROR_NO_INIT; diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 3c76115..359a7a9 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -32,6 +32,7 @@ import javax.microedition.khronos.opengles.GL10; import android.content.Context; import android.content.pm.ConfigurationInfo; import android.os.SystemProperties; +import android.os.Trace; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; @@ -1497,7 +1498,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { - view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceCreated"); + view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } createEglContext = false; } @@ -1508,7 +1514,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { - view.mRenderer.onSurfaceChanged(gl, w, h); + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSurfaceChanged"); + view.mRenderer.onSurfaceChanged(gl, w, h); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } sizeChanged = false; } @@ -1519,7 +1530,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback { GLSurfaceView view = mGLSurfaceViewWeakRef.get(); if (view != null) { - view.mRenderer.onDrawFrame(gl); + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onDrawFrame"); + view.mRenderer.onDrawFrame(gl); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } } int swapError = mEglHelper.swap(); diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index d876264..ddbcd78 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -336,12 +336,24 @@ public class CaptivePortalLoginActivity extends Activity { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - Log.w(TAG, "SSL error; displaying SSL warning."); + Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: " + + // Only show host to avoid leaking private info. + Uri.parse(error.getUrl()).getHost() + " certificate: " + + error.getCertificate() + "); displaying SSL warning."); final String html = String.format(SSL_ERROR_HTML, getString(R.string.ssl_error_warning), getString(R.string.ssl_error_example), mBrowserBailOutToken, getString(R.string.ssl_error_continue)); view.loadDataWithBaseURL(INTERNAL_ASSETS, html, "text/HTML", "UTF-8", null); } + + @Override + public boolean shouldOverrideUrlLoading (WebView view, String url) { + if (url.startsWith("tel:")) { + startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url))); + return true; + } + return false; + } } private class MyWebChromeClient extends WebChromeClient { diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am_alpha.png Binary files differindex 95b2b6d..67f890c 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_breadcrumb_arrow_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_accept_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_accept_alpha.png Binary files differdeleted file mode 100644 index ddf9a80..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_accept_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel_alpha.png Binary files differindex 7168197..1a9cd75 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_cancel_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_select_item_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_cab_select_item_alpha.png Binary files differdeleted file mode 100644 index 1c6ec6a..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_cab_select_item_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert_alpha.png Binary files differindex 11546a7..4c3d9a4 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_alert_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info_alpha.png Binary files differindex de5c113..da56077 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_dialog_info_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album_alpha.png Binary files differindex 769144a..2b21c12 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_album_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk_alpha.png Binary files differindex 22f95b9..ed3ee45 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_apk_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_alpha.png Binary files differindex c5152f4..1a3ebc4 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_doc_audio_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_folder_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_folder_alpha.png Binary files differdeleted file mode 100644 index eb71f81..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_folder_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy_alpha.png Binary files differindex 4997173..9a9e570 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_copy_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_disconnect_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_disconnect_am_alpha.png Binary files differdeleted file mode 100644 index 59eef4d..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_disconnect_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am_alpha.png Binary files differindex cd8987a..1d25a2d 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_new_folder_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_overflow_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_overflow_alpha.png Binary files differdeleted file mode 100644 index 95716b3..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_overflow_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_rename_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_rename_am_alpha.png Binary files differdeleted file mode 100644 index 76394c4..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_rename_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search_alpha.png Binary files differindex 941b93c..c593e7a 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_search_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_settings_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_settings_alpha.png Binary files differdeleted file mode 100644 index 4f5bcea..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_settings_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am_alpha.png Binary files differindex fad19c1..5e66488 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_sortby_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_undo_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_undo_am_alpha.png Binary files differdeleted file mode 100644 index df94033..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_undo_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid_alpha.png Binary files differindex 87154ad..7e15a8c 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_grid_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list_alpha.png Binary files differindex 9b40dae..c15537a 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_menu_view_list_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_open_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_open_am_alpha.png Binary files differdeleted file mode 100644 index 6b0d909..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_open_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_popout_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_popout_am_alpha.png Binary files differdeleted file mode 100644 index e1c39f8..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_popout_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_download_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_download_alpha.png Binary files differindex 092556a..d9aacea 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_root_download_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_root_download_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_folder_am_alpha.png Binary files differdeleted file mode 100644 index 223e619..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_root_folder_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent_alpha.png Binary files differindex 37fbdf7..9e003f0 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_root_recent_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard_alpha.png Binary files differindex 0929f93..65e42aa 100644 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard_alpha.png +++ b/packages/DocumentsUI/res/drawable-hdpi/ic_root_sdcard_alpha.png diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_root_usb_light_alpha.png b/packages/DocumentsUI/res/drawable-hdpi/ic_root_usb_light_alpha.png Binary files differdeleted file mode 100644 index 738ef84..0000000 --- a/packages/DocumentsUI/res/drawable-hdpi/ic_root_usb_light_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am_alpha.png Binary files differindex 897f7a9..9a048f1 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_breadcrumb_arrow_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_accept_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_accept_alpha.png Binary files differdeleted file mode 100644 index c9224a6..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_accept_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel_alpha.png Binary files differindex 3bb74f9..40a1a84 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_cancel_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_select_item_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_cab_select_item_alpha.png Binary files differdeleted file mode 100644 index 2d47376..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_cab_select_item_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert_alpha.png Binary files differindex 3868210..e768d11 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_alert_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info_alpha.png Binary files differindex a74854d..5ef3dc0 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_dialog_info_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album_alpha.png Binary files differindex f5e45a7..ac27eea 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_album_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk_alpha.png Binary files differindex 67e33ce..a4add51 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_apk_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_alpha.png Binary files differindex 34093d0..a9a7f20 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_doc_audio_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_folder_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_folder_alpha.png Binary files differdeleted file mode 100644 index 5ecddc5..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_folder_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy_alpha.png Binary files differindex f8a5262..c94cc28 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_copy_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_disconnect_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_disconnect_am_alpha.png Binary files differdeleted file mode 100644 index d8ebf49..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_disconnect_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am_alpha.png Binary files differindex 62c47d5..6e6b870 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_new_folder_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_overflow_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_overflow_alpha.png Binary files differdeleted file mode 100644 index 0825647..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_overflow_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_rename_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_rename_am_alpha.png Binary files differdeleted file mode 100644 index 9d9e3d7..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_rename_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search_alpha.png Binary files differindex 9fc1df6..6b16343 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_search_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_settings_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_settings_alpha.png Binary files differdeleted file mode 100644 index c95381b..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_settings_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am_alpha.png Binary files differindex f95d30d..04a12a4 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_sortby_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_undo_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_undo_am_alpha.png Binary files differdeleted file mode 100644 index 47c00dd..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_undo_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid_alpha.png Binary files differindex fe2c78c..5f968d5 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_grid_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list_alpha.png Binary files differindex 8b3a303..7a8eae9 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_menu_view_list_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_open_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_open_am_alpha.png Binary files differdeleted file mode 100644 index 5923c08..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_open_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_popout_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_popout_am_alpha.png Binary files differdeleted file mode 100644 index 0e45037..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_popout_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_download_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_download_alpha.png Binary files differindex a55f4ca..c2c845e 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_root_download_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_root_download_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_folder_am_alpha.png Binary files differdeleted file mode 100644 index 993e060..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_root_folder_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent_alpha.png Binary files differindex f359c4b..f500d58 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_root_recent_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard_alpha.png Binary files differindex dd76d07..1de8292 100644 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard_alpha.png +++ b/packages/DocumentsUI/res/drawable-mdpi/ic_root_sdcard_alpha.png diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_root_usb_light_alpha.png b/packages/DocumentsUI/res/drawable-mdpi/ic_root_usb_light_alpha.png Binary files differdeleted file mode 100644 index fccd45e..0000000 --- a/packages/DocumentsUI/res/drawable-mdpi/ic_root_usb_light_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am_alpha.png Binary files differindex 5d0805e..073583e 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_breadcrumb_arrow_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_accept_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_accept_alpha.png Binary files differdeleted file mode 100644 index 1a3d7c4..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_accept_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel_alpha.png Binary files differindex 14b9aef..6bc4372 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_cancel_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_select_item_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_select_item_alpha.png Binary files differdeleted file mode 100644 index 953ab62..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_cab_select_item_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert_alpha.png Binary files differindex 5198b84..2ea6164 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_alert_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info_alpha.png Binary files differindex cdf8c95..46ed12a 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_dialog_info_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album_alpha.png Binary files differindex aecd4cf..4203d35 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_album_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk_alpha.png Binary files differindex 24ad2dd..41558f2 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_apk_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_alpha.png Binary files differindex bf9dbde..e190c4d 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_doc_audio_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_folder_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_folder_alpha.png Binary files differdeleted file mode 100644 index cd24dfe..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_folder_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy_alpha.png Binary files differindex 9afd1a1..1cf76a9 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_copy_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_disconnect_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_disconnect_am_alpha.png Binary files differdeleted file mode 100644 index 95b0bb8..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_disconnect_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am_alpha.png Binary files differindex 14e96e2..49272b0 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_new_folder_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_overflow_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_overflow_alpha.png Binary files differdeleted file mode 100644 index 311e4dc..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_overflow_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_rename_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_rename_am_alpha.png Binary files differdeleted file mode 100644 index e2e3925..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_rename_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search_alpha.png Binary files differindex 6b2ddad..6381902 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_search_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_settings_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_settings_alpha.png Binary files differdeleted file mode 100644 index 304435a..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_settings_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am_alpha.png Binary files differindex 27e6acf..9e4fd61 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_sortby_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_undo_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_undo_am_alpha.png Binary files differdeleted file mode 100644 index 146b196..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_undo_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid_alpha.png Binary files differindex cc4d40c..630188c 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_grid_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list_alpha.png Binary files differindex 6c897c4..73372f4 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_menu_view_list_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_open_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_open_am_alpha.png Binary files differdeleted file mode 100644 index be6fb5d..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_open_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_popout_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_popout_am_alpha.png Binary files differdeleted file mode 100644 index 7ba284f..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_popout_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download_alpha.png Binary files differindex 9bda62c..f5afb24 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_download_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_folder_am_alpha.png Binary files differdeleted file mode 100644 index 24dce60..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_folder_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent_alpha.png Binary files differindex 5ef3288..fe71c25 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_recent_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard_alpha.png Binary files differindex 99b8054..00b8a8b 100644 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard_alpha.png +++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_sdcard_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_usb_light_alpha.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_root_usb_light_alpha.png Binary files differdeleted file mode 100644 index 80717e4..0000000 --- a/packages/DocumentsUI/res/drawable-xhdpi/ic_root_usb_light_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am_alpha.png Binary files differindex 02a988b..b96cfd1 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_breadcrumb_arrow_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_accept_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_accept_alpha.png Binary files differdeleted file mode 100644 index a23a9ae..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_accept_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel_alpha.png Binary files differindex b10db3e..51b4401 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_cancel_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_select_item_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_select_item_alpha.png Binary files differdeleted file mode 100644 index 3606fb3..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_cab_select_item_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert_alpha.png Binary files differindex 0a54598..ed36f70 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_alert_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info_alpha.png Binary files differindex 6d3260c..a81eeb9 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dialog_info_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album_alpha.png Binary files differindex 31dc7a4..60f59f5 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_album_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk_alpha.png Binary files differindex 5d0f0db..6006b12 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_apk_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_alpha.png Binary files differindex 8116b62..7926188 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_doc_audio_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_folder_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_folder_alpha.png Binary files differdeleted file mode 100644 index 02249d2..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_folder_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy_alpha.png Binary files differindex 90d1a09..074ea88 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_copy_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_disconnect_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_disconnect_am_alpha.png Binary files differdeleted file mode 100644 index 7a993b9..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_disconnect_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am_alpha.png Binary files differindex 12d279b..5c4360a 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_new_folder_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_overflow_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_overflow_alpha.png Binary files differdeleted file mode 100644 index 325c48a..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_overflow_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_rename_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_rename_am_alpha.png Binary files differdeleted file mode 100644 index ee2465db..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_rename_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search_alpha.png Binary files differindex 538fbe5..3ae490e 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_search_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_settings_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_settings_alpha.png Binary files differdeleted file mode 100644 index d99206c..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_settings_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am_alpha.png Binary files differindex 4f1355c..cb9d196 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_sortby_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_undo_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_undo_am_alpha.png Binary files differdeleted file mode 100644 index d5cc56f..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_undo_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid_alpha.png Binary files differindex de96c3f..7560f62 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_grid_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list_alpha.png Binary files differindex 7805834..b9483c3 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_menu_view_list_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_open_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_open_am_alpha.png Binary files differdeleted file mode 100644 index 7ed919c..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_open_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_popout_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_popout_am_alpha.png Binary files differdeleted file mode 100644 index 756b684..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_popout_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download_alpha.png Binary files differindex ffb9841..ce97c85 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_download_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_folder_am_alpha.png Binary files differdeleted file mode 100644 index 305a6b8..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_folder_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent_alpha.png Binary files differindex dc1425d..1dad2a8 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_recent_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard_alpha.png Binary files differindex cea8ac0..5236774 100644 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_sdcard_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_usb_light_alpha.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_usb_light_alpha.png Binary files differdeleted file mode 100644 index e0a1d23..0000000 --- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_root_usb_light_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_breadcrumb_arrow_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_breadcrumb_arrow_am_alpha.png Binary files differindex 84573f6..6f2dc5b 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_breadcrumb_arrow_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_breadcrumb_arrow_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_accept_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_accept_alpha.png Binary files differdeleted file mode 100644 index 986d3fb..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_accept_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_cancel_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_cancel_alpha.png Binary files differindex ce443f9..df42fee 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_cancel_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_cancel_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_select_item_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_select_item_alpha.png Binary files differdeleted file mode 100644 index a54e0ea..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_cab_select_item_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_alert_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_alert_alpha.png Binary files differindex 6e65716..3f4d539 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_alert_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_alert_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_info_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_info_alpha.png Binary files differindex 35bd56e..c8f86b9 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_info_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_dialog_info_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_album_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_album_alpha.png Binary files differindex 64aa50b..d2dd494 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_album_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_album_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_apk_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_apk_alpha.png Binary files differindex 7cbcb8b..4f935bf 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_apk_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_apk_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_audio_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_audio_alpha.png Binary files differindex d6d79ec..7499cbc 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_audio_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_doc_audio_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_folder_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_folder_alpha.png Binary files differdeleted file mode 100644 index c0c27a2..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_folder_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_copy_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_copy_alpha.png Binary files differindex 2dcf03e..1f6af72 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_copy_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_copy_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_disconnect_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_disconnect_am_alpha.png Binary files differdeleted file mode 100644 index d7eb04f..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_disconnect_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_new_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_new_folder_am_alpha.png Binary files differindex 879bf45..073d8533 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_new_folder_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_new_folder_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_overflow_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_overflow_alpha.png Binary files differdeleted file mode 100644 index 2cd4137..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_overflow_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_rename_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_rename_am_alpha.png Binary files differdeleted file mode 100644 index 268049e..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_rename_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_search_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_search_alpha.png Binary files differindex 78fc61d..21be572 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_search_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_search_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_settings_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_settings_alpha.png Binary files differdeleted file mode 100644 index 36e8e68..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_settings_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_sortby_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_sortby_am_alpha.png Binary files differindex 827a92c..631663a 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_sortby_am_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_sortby_am_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_undo_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_undo_am_alpha.png Binary files differdeleted file mode 100644 index 0f12b7d..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_undo_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_grid_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_grid_alpha.png Binary files differindex df1bac1..5d1e8d9 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_grid_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_grid_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_list_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_list_alpha.png Binary files differindex 9420374..7c1506b 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_list_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_menu_view_list_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_open_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_open_am_alpha.png Binary files differdeleted file mode 100644 index 0394c5c..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_open_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_popout_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_popout_am_alpha.png Binary files differdeleted file mode 100644 index 325d374..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_popout_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_download_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_download_alpha.png Binary files differindex 87a5210..8c83bff 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_download_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_download_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_folder_am_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_folder_am_alpha.png Binary files differdeleted file mode 100644 index bb41a91..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_folder_am_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_recent_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_recent_alpha.png Binary files differindex baa723d..68df974 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_recent_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_recent_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_sdcard_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_sdcard_alpha.png Binary files differindex 39c737f..c7daa2a 100644 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_sdcard_alpha.png +++ b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_sdcard_alpha.png diff --git a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_usb_light_alpha.png b/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_usb_light_alpha.png Binary files differdeleted file mode 100644 index 88973e0..0000000 --- a/packages/DocumentsUI/res/drawable-xxxhdpi/ic_root_usb_light_alpha.png +++ /dev/null diff --git a/packages/DocumentsUI/res/drawable/ic_cab_accept.xml b/packages/DocumentsUI/res/drawable/ic_cab_accept.xml deleted file mode 100644 index cda2845..0000000 --- a/packages/DocumentsUI/res/drawable/ic_cab_accept.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_cab_accept_alpha" - android:tint="?android:attr/colorControlNormal" /> diff --git a/packages/DocumentsUI/res/drawable/ic_cab_select_item.xml b/packages/DocumentsUI/res/drawable/ic_cab_select_item.xml deleted file mode 100644 index e440ee9..0000000 --- a/packages/DocumentsUI/res/drawable/ic_cab_select_item.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_cab_select_item_alpha" - android:tint="?android:attr/colorControlNormal" /> diff --git a/packages/DocumentsUI/res/drawable/ic_folder.xml b/packages/DocumentsUI/res/drawable/ic_folder.xml deleted file mode 100644 index 8adaf3b..0000000 --- a/packages/DocumentsUI/res/drawable/ic_folder.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_folder_alpha" - android:tint="?android:attr/colorControlNormal" /> diff --git a/packages/DocumentsUI/res/drawable/ic_menu_disconnect.xml b/packages/DocumentsUI/res/drawable/ic_menu_disconnect.xml deleted file mode 100644 index 15c5c39..0000000 --- a/packages/DocumentsUI/res/drawable/ic_menu_disconnect.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_menu_disconnect_am_alpha" - android:tint="?android:attr/colorControlNormal" - android:autoMirrored="true" /> diff --git a/packages/DocumentsUI/res/drawable/ic_menu_overflow.xml b/packages/DocumentsUI/res/drawable/ic_menu_overflow.xml deleted file mode 100644 index a579b60..0000000 --- a/packages/DocumentsUI/res/drawable/ic_menu_overflow.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_menu_overflow_alpha" - android:tint="?android:attr/colorControlNormal" /> diff --git a/packages/DocumentsUI/res/drawable/ic_menu_rename.xml b/packages/DocumentsUI/res/drawable/ic_menu_rename.xml deleted file mode 100644 index b5ba30c..0000000 --- a/packages/DocumentsUI/res/drawable/ic_menu_rename.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_menu_rename_am_alpha" - android:tint="?android:attr/colorControlNormal" - android:autoMirrored="true" /> diff --git a/packages/DocumentsUI/res/drawable/ic_menu_settings.xml b/packages/DocumentsUI/res/drawable/ic_menu_settings.xml deleted file mode 100644 index 7606e75..0000000 --- a/packages/DocumentsUI/res/drawable/ic_menu_settings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_menu_settings_alpha" - android:tint="?android:attr/colorControlNormal" /> diff --git a/packages/DocumentsUI/res/drawable/ic_menu_undo.xml b/packages/DocumentsUI/res/drawable/ic_menu_undo.xml deleted file mode 100644 index d66ba6d..0000000 --- a/packages/DocumentsUI/res/drawable/ic_menu_undo.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_menu_undo_am_alpha" - android:tint="?android:attr/colorControlNormal" - android:autoMirrored="true" /> diff --git a/packages/DocumentsUI/res/drawable/ic_open.xml b/packages/DocumentsUI/res/drawable/ic_open.xml deleted file mode 100644 index e3f1faf..0000000 --- a/packages/DocumentsUI/res/drawable/ic_open.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_open_am_alpha" - android:tint="?android:attr/colorControlNormal" - android:autoMirrored="true" /> diff --git a/packages/DocumentsUI/res/drawable/ic_popout.xml b/packages/DocumentsUI/res/drawable/ic_popout.xml deleted file mode 100644 index e7d0f75..0000000 --- a/packages/DocumentsUI/res/drawable/ic_popout.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_popout_am_alpha" - android:tint="?android:attr/colorControlNormal" - android:autoMirrored="true" /> diff --git a/packages/DocumentsUI/res/drawable/ic_root_folder.xml b/packages/DocumentsUI/res/drawable/ic_root_folder.xml deleted file mode 100644 index 01df07b..0000000 --- a/packages/DocumentsUI/res/drawable/ic_root_folder.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_root_folder_am_alpha" - android:tint="?android:attr/colorControlNormal" - android:autoMirrored="true" /> diff --git a/packages/DocumentsUI/res/drawable/ic_root_usb_light.xml b/packages/DocumentsUI/res/drawable/ic_root_usb_light.xml deleted file mode 100644 index 5f80dd9..0000000 --- a/packages/DocumentsUI/res/drawable/ic_root_usb_light.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/ic_root_usb_light_alpha" - android:tint="?android:attr/colorControlNormal" /> diff --git a/packages/DocumentsUI/res/layout-sw720dp/activity.xml b/packages/DocumentsUI/res/layout-sw720dp/activity.xml index 7b40a0f..a9f1b3c 100644 --- a/packages/DocumentsUI/res/layout-sw720dp/activity.xml +++ b/packages/DocumentsUI/res/layout-sw720dp/activity.xml @@ -41,7 +41,9 @@ android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal" - android:baselineAligned="false"> + android:baselineAligned="false" + android:divider="?android:attr/dividerVertical" + android:showDividers="middle"> <FrameLayout android:id="@+id/container_roots" diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml index eacafd5..3873593 100644 --- a/packages/SettingsLib/AndroidManifest.xml +++ b/packages/SettingsLib/AndroidManifest.xml @@ -16,5 +16,9 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.settingslib"> + package="com.android.settingslib"> + + <uses-sdk + android:minSdkVersion="21" /> + </manifest> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java new file mode 100644 index 0000000..b5e53c0 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -0,0 +1,1347 @@ +/* + * 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.settingslib.applications; + +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.PackageManager; +import android.content.pm.PackageStats; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.format.Formatter; +import android.util.Log; +import android.util.SparseArray; + +import java.io.File; +import java.text.Collator; +import java.text.Normalizer; +import java.text.Normalizer.Form; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Keeps track of information about all installed applications, lazy-loading + * as needed. + */ +public class ApplicationsState { + static final String TAG = "ApplicationsState"; + static final boolean DEBUG = false; + static final boolean DEBUG_LOCKING = false; + + public static final int SIZE_UNKNOWN = -1; + public static final int SIZE_INVALID = -2; + + static final Pattern REMOVE_DIACRITICALS_PATTERN + = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); + + static final Object sLock = new Object(); + static ApplicationsState sInstance; + + public static ApplicationsState getInstance(Application app) { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new ApplicationsState(app); + } + return sInstance; + } + } + + final Context mContext; + final PackageManager mPm; + final IPackageManager mIpm; + final UserManager mUm; + final int mOwnerRetrieveFlags; + final int mRetrieveFlags; + PackageIntentReceiver mPackageIntentReceiver; + + boolean mResumed; + boolean mHaveDisabledApps; + + // Information about all applications. Synchronize on mEntriesMap + // to protect access to these. + final ArrayList<Session> mSessions = new ArrayList<Session>(); + final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>(); + final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); + // Map: userid => (Map: package name => AppEntry) + final SparseArray<HashMap<String, AppEntry>> mEntriesMap = + new SparseArray<HashMap<String, AppEntry>>(); + final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); + List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); + long mCurId = 1; + String mCurComputingSizePkg; + int mCurComputingSizeUserId; + boolean mSessionsChanged; + + // Temporary for dispatching session callbacks. Only touched by main thread. + final ArrayList<Session> mActiveSessions = new ArrayList<Session>(); + + final HandlerThread mThread; + final BackgroundHandler mBackgroundHandler; + final MainHandler mMainHandler = new MainHandler(); + + private ApplicationsState(Application app) { + mContext = app; + mPm = mContext.getPackageManager(); + mIpm = AppGlobals.getPackageManager(); + mUm = (UserManager) app.getSystemService(Context.USER_SERVICE); + for (UserHandle user : mUm.getUserProfiles()) { + mEntriesMap.put(user.getIdentifier(), new HashMap<String, AppEntry>()); + } + mThread = new HandlerThread("ApplicationsState.Loader", + Process.THREAD_PRIORITY_BACKGROUND); + mThread.start(); + mBackgroundHandler = new BackgroundHandler(mThread.getLooper()); + + // Only the owner can see all apps. + mOwnerRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES | + PackageManager.GET_DISABLED_COMPONENTS | + PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS; + mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS | + PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS; + + /** + * This is a trick to prevent the foreground thread from being delayed. + * The problem is that Dalvik monitors are initially spin locks, to keep + * them lightweight. This leads to unfair contention -- Even though the + * background thread only holds the lock for a short amount of time, if + * it keeps running and locking again it can prevent the main thread from + * acquiring its lock for a long time... sometimes even > 5 seconds + * (leading to an ANR). + * + * Dalvik will promote a monitor to a "real" lock if it detects enough + * contention on it. It doesn't figure this out fast enough for us + * here, though, so this little trick will force it to turn into a real + * lock immediately. + */ + synchronized (mEntriesMap) { + try { + mEntriesMap.wait(1); + } catch (InterruptedException e) { + } + } + } + + public Looper getBackgroundLooper() { + return mThread.getLooper(); + } + + public Session newSession(Callbacks callbacks) { + Session s = new Session(callbacks); + synchronized (mEntriesMap) { + mSessions.add(s); + } + return s; + } + + void doResumeIfNeededLocked() { + if (mResumed) { + return; + } + mResumed = true; + if (mPackageIntentReceiver == null) { + mPackageIntentReceiver = new PackageIntentReceiver(); + mPackageIntentReceiver.registerReceiver(); + } + mApplications = new ArrayList<ApplicationInfo>(); + for (UserHandle user : mUm.getUserProfiles()) { + try { + // If this user is new, it needs a map created. + if (mEntriesMap.indexOfKey(user.getIdentifier()) < 0) { + mEntriesMap.put(user.getIdentifier(), new HashMap<String, AppEntry>()); + } + @SuppressWarnings("unchecked") + ParceledListSlice<ApplicationInfo> list = + mIpm.getInstalledApplications( + user.isOwner() ? mOwnerRetrieveFlags : mRetrieveFlags, + user.getIdentifier()); + mApplications.addAll(list.getList()); + } catch (RemoteException e) { + } + } + + if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { + // If an interesting part of the configuration has changed, we + // should completely reload the app entries. + clearEntries(); + } else { + for (int i=0; i<mAppEntries.size(); i++) { + mAppEntries.get(i).sizeStale = true; + } + } + + mHaveDisabledApps = false; + for (int i=0; i<mApplications.size(); i++) { + final ApplicationInfo info = mApplications.get(i); + // Need to trim out any applications that are disabled by + // something different than the user. + if (!info.enabled) { + if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + mApplications.remove(i); + i--; + continue; + } + mHaveDisabledApps = true; + } + int userId = UserHandle.getUserId(info.uid); + final AppEntry entry = mEntriesMap.get(userId).get(info.packageName); + if (entry != null) { + entry.info = info; + } + } + if (mAppEntries.size() > mApplications.size()) { + // There are less apps now, some must have been uninstalled. + clearEntries(); + } + mCurComputingSizePkg = null; + if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { + mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); + } + } + + private void clearEntries() { + for (int i = 0; i < mEntriesMap.size(); i++) { + mEntriesMap.valueAt(i).clear(); + } + mAppEntries.clear(); + } + + public boolean haveDisabledApps() { + return mHaveDisabledApps; + } + + void doPauseIfNeededLocked() { + if (!mResumed) { + return; + } + for (int i=0; i<mSessions.size(); i++) { + if (mSessions.get(i).mResumed) { + return; + } + } + doPauseLocked(); + } + + void doPauseLocked() { + mResumed = false; + if (mPackageIntentReceiver != null) { + mPackageIntentReceiver.unregisterReceiver(); + mPackageIntentReceiver = null; + } + } + + public AppEntry getEntry(String packageName, int userId) { + if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock..."); + synchronized (mEntriesMap) { + AppEntry entry = mEntriesMap.get(userId).get(packageName); + if (entry == null) { + ApplicationInfo info = getAppInfoLocked(packageName, userId); + if (info == null) { + try { + info = mIpm.getApplicationInfo(packageName, 0, userId); + } catch (RemoteException e) { + Log.w(TAG, "getEntry couldn't reach PackageManager", e); + return null; + } + } + entry = getEntryLocked(info); + } + if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock"); + return entry; + } + } + + private ApplicationInfo getAppInfoLocked(String pkg, int userId) { + for (int i = 0; i < mApplications.size(); i++) { + ApplicationInfo info = mApplications.get(i); + if (pkg.equals(info.packageName) + && userId == UserHandle.getUserId(info.uid)) { + return info; + } + } + return null; + } + + public void ensureIcon(AppEntry entry) { + if (entry.icon != null) { + return; + } + synchronized (entry) { + entry.ensureIconLocked(mContext, mPm); + } + } + + public void requestSize(String packageName, int userId) { + if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock..."); + synchronized (mEntriesMap) { + AppEntry entry = mEntriesMap.get(userId).get(packageName); + if (entry != null) { + mPm.getPackageSizeInfo(packageName, userId, mBackgroundHandler.mStatsObserver); + } + if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock"); + } + } + + long sumCacheSizes() { + long sum = 0; + if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock..."); + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock"); + for (int i=mAppEntries.size()-1; i>=0; i--) { + sum += mAppEntries.get(i).cacheSize; + } + if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock"); + } + return sum; + } + + int indexOfApplicationInfoLocked(String pkgName, int userId) { + for (int i=mApplications.size()-1; i>=0; i--) { + ApplicationInfo appInfo = mApplications.get(i); + if (appInfo.packageName.equals(pkgName) + && UserHandle.getUserId(appInfo.uid) == userId) { + return i; + } + } + return -1; + } + + void addPackage(String pkgName, int userId) { + try { + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock"); + if (DEBUG) Log.i(TAG, "Adding package " + pkgName); + if (!mResumed) { + // If we are not resumed, we will do a full query the + // next time we resume, so there is no reason to do work + // here. + if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed"); + return; + } + if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) { + if (DEBUG) Log.i(TAG, "Package already exists!"); + if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists"); + return; + } + ApplicationInfo info = mIpm.getApplicationInfo(pkgName, + userId == UserHandle.USER_OWNER ? mOwnerRetrieveFlags : mRetrieveFlags, + userId); + if (info == null) { + return; + } + if (!info.enabled) { + if (info.enabledSetting + != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + return; + } + mHaveDisabledApps = true; + } + mApplications.add(info); + if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { + mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); + } + if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); + } + if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock"); + } + } catch (RemoteException e) { + } + } + + public void removePackage(String pkgName, int userId) { + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock"); + int idx = indexOfApplicationInfoLocked(pkgName, userId); + if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx); + if (idx >= 0) { + AppEntry entry = mEntriesMap.get(userId).get(pkgName); + if (DEBUG) Log.i(TAG, "removePackage: " + entry); + if (entry != null) { + mEntriesMap.get(userId).remove(pkgName); + mAppEntries.remove(entry); + } + ApplicationInfo info = mApplications.get(idx); + mApplications.remove(idx); + if (!info.enabled) { + mHaveDisabledApps = false; + for (int i=0; i<mApplications.size(); i++) { + if (!mApplications.get(i).enabled) { + mHaveDisabledApps = true; + break; + } + } + } + if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); + } + } + if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock"); + } + } + + public void invalidatePackage(String pkgName, int userId) { + removePackage(pkgName, userId); + addPackage(pkgName, userId); + } + + private void addUser(int userId) { + if (mUm.getUserProfiles().contains(new UserHandle(userId))) { + synchronized (mEntriesMap) { + mEntriesMap.put(userId, new HashMap<String, AppEntry>()); + if (mResumed) { + // If resumed, Manually pause, then cause a resume to repopulate the app list. + // This is the simplest way to reload the packages so that the new user + // is included. Otherwise the list will be repopulated on next resume. + doPauseLocked(); + doResumeIfNeededLocked(); + } + if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); + } + } + } + } + + private void removeUser(int userId) { + synchronized (mEntriesMap) { + HashMap<String, AppEntry> userMap = mEntriesMap.get(userId); + if (userMap != null) { + for (AppEntry appEntry : userMap.values()) { + mAppEntries.remove(appEntry); + mApplications.remove(appEntry.info); + } + mEntriesMap.remove(userId); + if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); + } + } + } + } + + private AppEntry getEntryLocked(ApplicationInfo info) { + int userId = UserHandle.getUserId(info.uid); + AppEntry entry = mEntriesMap.get(userId).get(info.packageName); + if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry); + if (entry == null) { + if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName); + entry = new AppEntry(mContext, info, mCurId++); + mEntriesMap.get(userId).put(info.packageName, entry); + mAppEntries.add(entry); + } else if (entry.info != info) { + entry.info = info; + } + return entry; + } + + // -------------------------------------------------------------- + + private long getTotalInternalSize(PackageStats ps) { + if (ps != null) { + return ps.codeSize + ps.dataSize; + } + return SIZE_INVALID; + } + + private long getTotalExternalSize(PackageStats ps) { + if (ps != null) { + // We also include the cache size here because for non-emulated + // we don't automtically clean cache files. + return ps.externalCodeSize + ps.externalDataSize + + ps.externalCacheSize + + ps.externalMediaSize + ps.externalObbSize; + } + return SIZE_INVALID; + } + + private String getSizeStr(long size) { + if (size >= 0) { + return Formatter.formatFileSize(mContext, size); + } + return null; + } + + void rebuildActiveSessions() { + synchronized (mEntriesMap) { + if (!mSessionsChanged) { + return; + } + mActiveSessions.clear(); + for (int i=0; i<mSessions.size(); i++) { + Session s = mSessions.get(i); + if (s.mResumed) { + mActiveSessions.add(s); + } + } + } + } + + public static String normalize(String str) { + String tmp = Normalizer.normalize(str, Form.NFD); + return REMOVE_DIACRITICALS_PATTERN.matcher(tmp) + .replaceAll("").toLowerCase(); + } + + public class Session { + final Callbacks mCallbacks; + boolean mResumed; + + // Rebuilding of app list. Synchronized on mRebuildSync. + final Object mRebuildSync = new Object(); + boolean mRebuildRequested; + boolean mRebuildAsync; + AppFilter mRebuildFilter; + Comparator<AppEntry> mRebuildComparator; + ArrayList<AppEntry> mRebuildResult; + ArrayList<AppEntry> mLastAppList; + + Session(Callbacks callbacks) { + mCallbacks = callbacks; + } + + public void resume() { + if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); + synchronized (mEntriesMap) { + if (!mResumed) { + mResumed = true; + mSessionsChanged = true; + doResumeIfNeededLocked(); + } + } + if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); + } + + public void pause() { + if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); + synchronized (mEntriesMap) { + if (mResumed) { + mResumed = false; + mSessionsChanged = true; + mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this); + doPauseIfNeededLocked(); + } + if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); + } + } + + public ArrayList<AppEntry> getAllApps() { + synchronized (mEntriesMap) { + return new ArrayList<>(mAppEntries); + } + } + + // Creates a new list of app entries with the given filter and comparator. + public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { + synchronized (mRebuildSync) { + synchronized (mEntriesMap) { + mRebuildingSessions.add(this); + mRebuildRequested = true; + mRebuildAsync = false; + mRebuildFilter = filter; + mRebuildComparator = comparator; + mRebuildResult = null; + if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { + Message msg = mBackgroundHandler.obtainMessage( + BackgroundHandler.MSG_REBUILD_LIST); + mBackgroundHandler.sendMessage(msg); + } + } + + // We will wait for .25s for the list to be built. + long waitend = SystemClock.uptimeMillis()+250; + + while (mRebuildResult == null) { + long now = SystemClock.uptimeMillis(); + if (now >= waitend) { + break; + } + try { + mRebuildSync.wait(waitend - now); + } catch (InterruptedException e) { + } + } + + mRebuildAsync = true; + + return mRebuildResult; + } + } + + void handleRebuildList() { + AppFilter filter; + Comparator<AppEntry> comparator; + synchronized (mRebuildSync) { + if (!mRebuildRequested) { + return; + } + + filter = mRebuildFilter; + comparator = mRebuildComparator; + mRebuildRequested = false; + mRebuildFilter = null; + mRebuildComparator = null; + } + + Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); + + if (filter != null) { + filter.init(); + } + + List<AppEntry> apps; + synchronized (mEntriesMap) { + apps = new ArrayList<>(mAppEntries); + } + + ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); + if (DEBUG) Log.i(TAG, "Rebuilding..."); + for (int i=0; i<apps.size(); i++) { + AppEntry entry = apps.get(i); + if (filter == null || filter.filterApp(entry)) { + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock"); + entry.ensureLabel(mContext); + if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry); + filteredApps.add(entry); + if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock"); + } + } + } + + Collections.sort(filteredApps, comparator); + + synchronized (mRebuildSync) { + if (!mRebuildRequested) { + mLastAppList = filteredApps; + if (!mRebuildAsync) { + mRebuildResult = filteredApps; + mRebuildSync.notifyAll(); + } else { + if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) { + Message msg = mMainHandler.obtainMessage( + MainHandler.MSG_REBUILD_COMPLETE, this); + mMainHandler.sendMessage(msg); + } + } + } + } + + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public void release() { + pause(); + synchronized (mEntriesMap) { + mSessions.remove(this); + } + } + } + + class MainHandler extends Handler { + static final int MSG_REBUILD_COMPLETE = 1; + static final int MSG_PACKAGE_LIST_CHANGED = 2; + static final int MSG_PACKAGE_ICON_CHANGED = 3; + static final int MSG_PACKAGE_SIZE_CHANGED = 4; + static final int MSG_ALL_SIZES_COMPUTED = 5; + static final int MSG_RUNNING_STATE_CHANGED = 6; + static final int MSG_LAUNCHER_INFO_CHANGED = 7; + static final int MSG_LOAD_ENTRIES_COMPLETE = 8; + + @Override + public void handleMessage(Message msg) { + rebuildActiveSessions(); + switch (msg.what) { + case MSG_REBUILD_COMPLETE: { + Session s = (Session)msg.obj; + if (mActiveSessions.contains(s)) { + s.mCallbacks.onRebuildComplete(s.mLastAppList); + } + } break; + case MSG_PACKAGE_LIST_CHANGED: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onPackageListChanged(); + } + } break; + case MSG_PACKAGE_ICON_CHANGED: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onPackageIconChanged(); + } + } break; + case MSG_PACKAGE_SIZE_CHANGED: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onPackageSizeChanged( + (String)msg.obj); + } + } break; + case MSG_ALL_SIZES_COMPUTED: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onAllSizesComputed(); + } + } break; + case MSG_RUNNING_STATE_CHANGED: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onRunningStateChanged( + msg.arg1 != 0); + } + } break; + case MSG_LAUNCHER_INFO_CHANGED: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged(); + } + } break; + case MSG_LOAD_ENTRIES_COMPLETE: { + for (int i=0; i<mActiveSessions.size(); i++) { + mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted(); + } + } break; + } + } + } + + private class BackgroundHandler extends Handler { + static final int MSG_REBUILD_LIST = 1; + static final int MSG_LOAD_ENTRIES = 2; + static final int MSG_LOAD_ICONS = 3; + static final int MSG_LOAD_SIZES = 4; + static final int MSG_LOAD_LAUNCHER = 5; + + boolean mRunning; + + BackgroundHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + // Always try rebuilding list first thing, if needed. + ArrayList<Session> rebuildingSessions = null; + synchronized (mEntriesMap) { + if (mRebuildingSessions.size() > 0) { + rebuildingSessions = new ArrayList<Session>(mRebuildingSessions); + mRebuildingSessions.clear(); + } + } + if (rebuildingSessions != null) { + for (int i=0; i<rebuildingSessions.size(); i++) { + rebuildingSessions.get(i).handleRebuildList(); + } + } + + switch (msg.what) { + case MSG_REBUILD_LIST: { + } break; + case MSG_LOAD_ENTRIES: { + int numDone = 0; + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock"); + for (int i = 0; i < mApplications.size() && numDone < 6; i++) { + if (!mRunning) { + mRunning = true; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 1); + mMainHandler.sendMessage(m); + } + ApplicationInfo info = mApplications.get(i); + int userId = UserHandle.getUserId(info.uid); + if (mEntriesMap.get(userId).get(info.packageName) == null) { + numDone++; + getEntryLocked(info); + } + } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock"); + } + + if (numDone >= 6) { + sendEmptyMessage(MSG_LOAD_ENTRIES); + } else { + if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE); + } + sendEmptyMessage(MSG_LOAD_LAUNCHER); + } + } break; + case MSG_LOAD_LAUNCHER: { + Intent launchIntent = new Intent(Intent.ACTION_MAIN, null) + .addCategory(Intent.CATEGORY_LAUNCHER); + + for (int i = 0; i < mEntriesMap.size(); i++) { + int userId = mEntriesMap.keyAt(i); + List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(launchIntent, + PackageManager.GET_DISABLED_COMPONENTS, userId); + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock"); + HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i); + final int N = intents.size(); + for (int j = 0; j < N; j++) { + String packageName = intents.get(j).activityInfo.packageName; + AppEntry entry = userEntries.get(packageName); + if (entry != null) { + entry.hasLauncherEntry = true; + } else { + Log.w(TAG, "Cannot find pkg: " + packageName + + " on user " + userId); + } + } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock"); + } + } + + if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED); + } + sendEmptyMessage(MSG_LOAD_ICONS); + } break; + case MSG_LOAD_ICONS: { + int numDone = 0; + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock"); + for (int i=0; i<mAppEntries.size() && numDone<2; i++) { + AppEntry entry = mAppEntries.get(i); + if (entry.icon == null || !entry.mounted) { + synchronized (entry) { + if (entry.ensureIconLocked(mContext, mPm)) { + if (!mRunning) { + mRunning = true; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 1); + mMainHandler.sendMessage(m); + } + numDone++; + } + } + } + } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock"); + } + if (numDone > 0) { + if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED); + } + } + if (numDone >= 2) { + sendEmptyMessage(MSG_LOAD_ICONS); + } else { + sendEmptyMessage(MSG_LOAD_SIZES); + } + } break; + case MSG_LOAD_SIZES: { + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); + if (mCurComputingSizePkg != null) { + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); + return; + } + + long now = SystemClock.uptimeMillis(); + for (int i=0; i<mAppEntries.size(); i++) { + AppEntry entry = mAppEntries.get(i); + if (entry.size == SIZE_UNKNOWN || entry.sizeStale) { + if (entry.sizeLoadStart == 0 || + (entry.sizeLoadStart < (now-20*1000))) { + if (!mRunning) { + mRunning = true; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 1); + mMainHandler.sendMessage(m); + } + entry.sizeLoadStart = now; + mCurComputingSizePkg = entry.info.packageName; + mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid); + mPm.getPackageSizeInfo(mCurComputingSizePkg, + mCurComputingSizeUserId, mStatsObserver); + } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); + return; + } + } + if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { + mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); + mRunning = false; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 0); + mMainHandler.sendMessage(m); + } + if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); + } + } break; + } + } + + final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { + public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { + boolean sizeChanged = false; + synchronized (mEntriesMap) { + if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock"); + HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle); + if (userMap == null) { + // The user must have been removed. + return; + } + AppEntry entry = userMap.get(stats.packageName); + if (entry != null) { + synchronized (entry) { + entry.sizeStale = false; + entry.sizeLoadStart = 0; + long externalCodeSize = stats.externalCodeSize + + stats.externalObbSize; + long externalDataSize = stats.externalDataSize + + stats.externalMediaSize; + long newSize = externalCodeSize + externalDataSize + + getTotalInternalSize(stats); + if (entry.size != newSize || + entry.cacheSize != stats.cacheSize || + entry.codeSize != stats.codeSize || + entry.dataSize != stats.dataSize || + entry.externalCodeSize != externalCodeSize || + entry.externalDataSize != externalDataSize || + entry.externalCacheSize != stats.externalCacheSize) { + entry.size = newSize; + entry.cacheSize = stats.cacheSize; + entry.codeSize = stats.codeSize; + entry.dataSize = stats.dataSize; + entry.externalCodeSize = externalCodeSize; + entry.externalDataSize = externalDataSize; + entry.externalCacheSize = stats.externalCacheSize; + entry.sizeStr = getSizeStr(entry.size); + entry.internalSize = getTotalInternalSize(stats); + entry.internalSizeStr = getSizeStr(entry.internalSize); + entry.externalSize = getTotalExternalSize(stats); + entry.externalSizeStr = getSizeStr(entry.externalSize); + if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry + + ": " + entry.sizeStr); + sizeChanged = true; + } + } + if (sizeChanged) { + Message msg = mMainHandler.obtainMessage( + MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName); + mMainHandler.sendMessage(msg); + } + } + if (mCurComputingSizePkg != null + && (mCurComputingSizePkg.equals(stats.packageName) + && mCurComputingSizeUserId == stats.userHandle)) { + mCurComputingSizePkg = null; + sendEmptyMessage(MSG_LOAD_SIZES); + } + if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock"); + } + } + }; + } + + /** + * Receives notifications when applications are added/removed. + */ + private class PackageIntentReceiver extends BroadcastReceiver { + void registerReceiver() { + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + mContext.registerReceiver(this, filter); + // Register for events related to sdcard installation. + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + mContext.registerReceiver(this, sdFilter); + // Register for events related to user creation/deletion. + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_ADDED); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(this, userFilter); + } + void unregisterReceiver() { + mContext.unregisterReceiver(this); + } + @Override + public void onReceive(Context context, Intent intent) { + String actionStr = intent.getAction(); + if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) { + Uri data = intent.getData(); + String pkgName = data.getEncodedSchemeSpecificPart(); + for (int i = 0; i < mEntriesMap.size(); i++) { + addPackage(pkgName, mEntriesMap.keyAt(i)); + } + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { + Uri data = intent.getData(); + String pkgName = data.getEncodedSchemeSpecificPart(); + for (int i = 0; i < mEntriesMap.size(); i++) { + removePackage(pkgName, mEntriesMap.keyAt(i)); + } + } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) { + Uri data = intent.getData(); + String pkgName = data.getEncodedSchemeSpecificPart(); + for (int i = 0; i < mEntriesMap.size(); i++) { + invalidatePackage(pkgName, mEntriesMap.keyAt(i)); + } + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) || + Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) { + // When applications become available or unavailable (perhaps because + // the SD card was inserted or ejected) we need to refresh the + // AppInfo with new label, icon and size information as appropriate + // given the newfound (un)availability of the application. + // A simple way to do that is to treat the refresh as a package + // removal followed by a package addition. + String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + if (pkgList == null || pkgList.length == 0) { + // Ignore + return; + } + boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr); + if (avail) { + for (String pkgName : pkgList) { + for (int i = 0; i < mEntriesMap.size(); i++) { + invalidatePackage(pkgName, mEntriesMap.keyAt(i)); + } + } + } + } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) { + addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)); + } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL)); + } + } + } + + public interface Callbacks { + void onRunningStateChanged(boolean running); + void onPackageListChanged(); + void onRebuildComplete(ArrayList<AppEntry> apps); + void onPackageIconChanged(); + void onPackageSizeChanged(String packageName); + void onAllSizesComputed(); + void onLauncherInfoChanged(); + void onLoadEntriesCompleted(); + } + + public static class SizeInfo { + public long cacheSize; + public long codeSize; + public long dataSize; + public long externalCodeSize; + public long externalDataSize; + + // This is the part of externalDataSize that is in the cache + // section of external storage. Note that we don't just combine + // this with cacheSize because currently the platform can't + // automatically trim this data when needed, so it is something + // the user may need to manage. The externalDataSize also includes + // this value, since what this is here is really the part of + // externalDataSize that we can just consider to be "cache" files + // for purposes of cleaning them up in the app details UI. + public long externalCacheSize; + } + + public static class AppEntry extends SizeInfo { + public final File apkFile; + public final long id; + public String label; + public long size; + public long internalSize; + public long externalSize; + + public boolean mounted; + + public boolean hasLauncherEntry; + + public String getNormalizedLabel() { + if (normalizedLabel != null) { + return normalizedLabel; + } + normalizedLabel = normalize(label); + return normalizedLabel; + } + + // Need to synchronize on 'this' for the following. + public ApplicationInfo info; + public Drawable icon; + public String sizeStr; + public String internalSizeStr; + public String externalSizeStr; + public boolean sizeStale; + public long sizeLoadStart; + + public String normalizedLabel; + + // A location where extra info can be placed to be used by custom filters. + public Object extraInfo; + + AppEntry(Context context, ApplicationInfo info, long id) { + apkFile = new File(info.sourceDir); + this.id = id; + this.info = info; + this.size = SIZE_UNKNOWN; + this.sizeStale = true; + ensureLabel(context); + } + + public void ensureLabel(Context context) { + if (this.label == null || !this.mounted) { + if (!this.apkFile.exists()) { + this.mounted = false; + this.label = info.packageName; + } else { + this.mounted = true; + CharSequence label = info.loadLabel(context.getPackageManager()); + this.label = label != null ? label.toString() : info.packageName; + } + } + } + + boolean ensureIconLocked(Context context, PackageManager pm) { + if (this.icon == null) { + if (this.apkFile.exists()) { + this.icon = getBadgedIcon(pm); + return true; + } else { + this.mounted = false; + this.icon = context.getDrawable( + com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); + } + } else if (!this.mounted) { + // If the app wasn't mounted but is now mounted, reload + // its icon. + if (this.apkFile.exists()) { + this.mounted = true; + this.icon = getBadgedIcon(pm); + return true; + } + } + return false; + } + + private Drawable getBadgedIcon(PackageManager pm) { + // Do badging ourself so that it comes from the user of the app not the current user. + return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info), + new UserHandle(UserHandle.getUserId(info.uid))); + } + + public String getVersion(Context context) { + try { + return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + return ""; + } + } + } + + public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppEntry object1, AppEntry object2) { + return sCollator.compare(object1.label, object2.label); + } + }; + + public static final Comparator<AppEntry> SIZE_COMPARATOR + = new Comparator<AppEntry>() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppEntry object1, AppEntry object2) { + if (object1.size < object2.size) return 1; + if (object1.size > object2.size) return -1; + return sCollator.compare(object1.label, object2.label); + } + }; + + public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR + = new Comparator<AppEntry>() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppEntry object1, AppEntry object2) { + if (object1.internalSize < object2.internalSize) return 1; + if (object1.internalSize > object2.internalSize) return -1; + return sCollator.compare(object1.label, object2.label); + } + }; + + public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR + = new Comparator<AppEntry>() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppEntry object1, AppEntry object2) { + if (object1.externalSize < object2.externalSize) return 1; + if (object1.externalSize > object2.externalSize) return -1; + return sCollator.compare(object1.label, object2.label); + } + }; + + public interface AppFilter { + void init(); + boolean filterApp(AppEntry info); + } + + public static final AppFilter FILTER_PERSONAL = new AppFilter() { + private int mCurrentUser; + + public void init() { + mCurrentUser = ActivityManager.getCurrentUser(); + } + + @Override + public boolean filterApp(AppEntry entry) { + return UserHandle.getUserId(entry.info.uid) == mCurrentUser; + } + }; + + public static final AppFilter FILTER_WORK = new AppFilter() { + private int mCurrentUser; + + public void init() { + mCurrentUser = ActivityManager.getCurrentUser(); + } + + @Override + public boolean filterApp(AppEntry entry) { + return UserHandle.getUserId(entry.info.uid) != mCurrentUser; + } + }; + + public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() { + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + return true; + } else if (entry.hasLauncherEntry) { + return true; + } + return false; + } + }; + + public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() { + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + return true; + } + return false; + } + }; + + public static final AppFilter FILTER_DISABLED = new AppFilter() { + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + return !entry.info.enabled; + } + }; + + public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() { + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + return entry.info.enabled; + } + }; + + public static final AppFilter FILTER_EVERYTHING = new AppFilter() { + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + return true; + } + }; + + public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() { + public void init() { + } + + @Override + public boolean filterApp(AppEntry entry) { + return (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0; + } + }; + + public static class VolumeFilter implements AppFilter { + private final String mVolumeUuid; + + public VolumeFilter(String volumeUuid) { + mVolumeUuid = volumeUuid; + } + + @Override + public void init() { + } + + @Override + public boolean filterApp(AppEntry info) { + return Objects.equals(info.info.volumeUuid, mVolumeUuid); + } + } + + public static class CompoundFilter implements AppFilter { + private final AppFilter mFirstFilter; + private final AppFilter mSecondFilter; + + public CompoundFilter(AppFilter first, AppFilter second) { + mFirstFilter = first; + mSecondFilter = second; + } + + @Override + public void init() { + mFirstFilter.init(); + mSecondFilter.init(); + } + + @Override + public boolean filterApp(AppEntry info) { + return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java new file mode 100644 index 0000000..d34dd89 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java @@ -0,0 +1,37 @@ +/* + * 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.settingslib.applications; + +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; + +public class InterestingConfigChanges { + private final Configuration mLastConfiguration = new Configuration(); + private int mLastDensity; + + public boolean applyNewConfig(Resources res) { + int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); + boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi; + if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE + |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) { + mLastDensity = res.getDisplayMetrics().densityDpi; + return true; + } + return false; + } +} diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 03b6dca..e42ce66 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -20,6 +20,7 @@ <!-- This is the combined status bar / notification panel window. --> <com.android.systemui.statusbar.phone.StatusBarWindowView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> @@ -29,6 +30,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" + sysui:ignoreRightInset="true" > <ImageView android:id="@+id/backdrop_back" android:layout_width="match_parent" @@ -44,7 +46,9 @@ <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" android:layout_height="match_parent" - android:importantForAccessibility="no" /> + android:importantForAccessibility="no" + sysui:ignoreRightInset="true" + /> <com.android.systemui.statusbar.AlphaOptimizedView android:id="@+id/heads_up_scrim" @@ -89,6 +93,8 @@ <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front" android:layout_width="match_parent" android:layout_height="match_parent" - android:importantForAccessibility="no" /> + android:importantForAccessibility="no" + sysui:ignoreRightInset="true" + /> </com.android.systemui.statusbar.phone.StatusBarWindowView> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 24f92ef..354b70b 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -82,5 +82,9 @@ <attr name="fillColor" format="integer" /> <attr name="singleToneColor" format="integer" /> </declare-styleable> + + <declare-styleable name="StatusBarWindowView_Layout"> + <attr name="ignoreRightInset" format="boolean" /> + </declare-styleable> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c026d77..0e0584f 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> @@ -911,7 +914,7 @@ <string name="monitoring_description_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information. For more information, contact your administrator.</string> <!-- Monitoring dialog VPN text [CHAR LIMIT=400] --> - <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and websites.</string> + <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string> <!-- Monitoring dialog VPN with device owner text [CHAR LIMIT=400] --> <string name="monitoring_description_vpn_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to a VPN, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string> @@ -923,16 +926,16 @@ <string name="legacy_vpn_name">VPN</string> <!-- Monitoring dialog text for single app (no profile or device owner) [CHAR LIMIT=400] --> - <string name="monitoring_description_app">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your network activity including emails, apps and websites.</string> + <string name="monitoring_description_app">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your network activity including emails, apps, and websites.</string> <!-- Monitoring dialog text for single app (inside personal profile) [CHAR LIMIT=400] --> - <string name="monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps and websites.</string> + <string name="monitoring_description_app_personal">You\'re connected to <xliff:g id="application">%1$s</xliff:g>, which can monitor your personal network activity, including emails, apps, and websites.</string> <!-- Monitoring dialog text for single app (inside work profile) [CHAR LIMIT=400] --> - <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nFor more information, contact your administrator.</string> + <string name="monitoring_description_app_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string> <!-- Monitoring dialog text for multiple apps (in personal and work profiles) [CHAR LIMIT=400] --> - <string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string> + <string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string> <!-- Monitoring dialog text for single app (with device owner) [CHAR LIMIT=400] --> <string name="monitoring_description_vpn_app_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string> @@ -1006,7 +1009,7 @@ <string name="volumeui_notification_text">Touch to restore the original.</string> <!-- Toast shown when user unlocks screen and managed profile activity is in the foreground --> - <string name="managed_profile_foreground_toast">You are in the Work profile</string> + <string name="managed_profile_foreground_toast">You are in the work profile</string> <string-array name="volume_stream_titles" translatable="false"> <item>Voice calls</item> <!-- STREAM_VOICE_CALL --> @@ -1087,4 +1090,10 @@ <!-- Alarm template for far alarms [CHAR LIMIT=25] --> <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string> + <!-- 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 6888d0e..6acd137 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 f129288..3e122da 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 f59e864..ca38528 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/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index cd4f299..25e3d10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -111,6 +111,8 @@ public class QSPanel extends ViewGroup { mDetailDoneButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { + announceForAccessibility( + mContext.getString(R.string.accessibility_desc_quick_settings)); closeDetail(); } }); @@ -392,6 +394,9 @@ public class QSPanel extends ViewGroup { mDetail.bringToFront(); mDetailContent.addView(r.detailView); MetricsLogger.visible(mContext, detailAdapter.getMetricsCategory()); + announceForAccessibility(mContext.getString( + R.string.accessibility_quick_settings_detail, + mContext.getString(detailAdapter.getTitle()))); setDetailRecord(r); listener = mHideGridContentWhenDone; if (r instanceof TileRecord) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 7d2b5c87..442af90 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 00665f4..a323684 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 13b3898..b93fc76 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 f11d83c..e70d146 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 569b918..a5b18f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -232,6 +232,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, /** Allow some time inbetween the long press for back and recents. */ private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; + /** If true, the system is in the half-boot-to-decryption-screen state. + * Prudently disable QS and notifications. */ private static final boolean ONLY_CORE_APPS; static { @@ -858,6 +860,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 0872e06..6a6266e 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/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 6a8f8ee..7f1fea1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import android.app.StatusBarManager; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; @@ -50,6 +51,8 @@ public class StatusBarWindowView extends FrameLayout { private NotificationPanelView mNotificationPanel; private View mBrightnessMirror; + private int mRightInset = 0; + PhoneStatusBar mService; private final Paint mTransparentSrcPaint = new Paint(); @@ -63,14 +66,18 @@ public class StatusBarWindowView extends FrameLayout { @Override protected boolean fitSystemWindows(Rect insets) { if (getFitsSystemWindows()) { - boolean changed = insets.left != getPaddingLeft() + boolean paddingChanged = insets.left != getPaddingLeft() || insets.top != getPaddingTop() - || insets.right != getPaddingRight() || insets.bottom != getPaddingBottom(); - // Drop top inset, apply right and left inset and pass through bottom inset. - if (changed) { - setPadding(insets.left, 0, insets.right, 0); + // Super-special right inset handling, because scrims and backdrop need to ignore it. + if (insets.right != mRightInset) { + mRightInset = insets.right; + applyMargins(); + } + // Drop top inset, apply left inset and pass through bottom inset. + if (paddingChanged) { + setPadding(insets.left, 0, 0, 0); } insets.left = 0; insets.top = 0; @@ -88,6 +95,30 @@ public class StatusBarWindowView extends FrameLayout { return false; } + private void applyMargins() { + final int N = getChildCount(); + for (int i = 0; i < N; i++) { + View child = getChildAt(i); + if (child.getLayoutParams() instanceof LayoutParams) { + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (!lp.ignoreRightInset && lp.rightMargin != mRightInset) { + lp.rightMargin = mRightInset; + child.requestLayout(); + } + } + } + } + + @Override + public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected FrameLayout.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + @Override protected void onAttachedToWindow () { super.onAttachedToWindow(); @@ -244,5 +275,23 @@ public class StatusBarWindowView extends FrameLayout { mStackScrollLayout.cancelExpandHelper(); } } + + public class LayoutParams extends FrameLayout.LayoutParams { + + public boolean ignoreRightInset; + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + + TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); + ignoreRightInset = a.getBoolean( + R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); + a.recycle(); + } + } } 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 e1e022d..40984d4 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 4f47cc6..962000a 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 7472af9..2b76c31 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/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index 310a64c..29bea4d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -386,18 +386,8 @@ public class VolumeDialog { } } } else { - if (mAutomute && !row.ss.muteSupported) { - final boolean vmute = row.ss.level == 0; - mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0); - } else { - final boolean mute = !row.ss.muted; - mController.setStreamMute(stream, mute); - if (mAutomute) { - if (!mute && row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } - } - } + final boolean vmute = row.ss.level == 0; + mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0); } row.userAttempt = 0; // reset the grace period, slider should update immediately } @@ -589,6 +579,9 @@ public class VolumeDialog { if (ss.level > 0) { row.lastAudibleLevel = ss.level; } + if (ss.level == row.requestedLevel) { + row.requestedLevel = -1; + } final boolean isRingStream = row.stream == AudioManager.STREAM_RING; final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM; final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM; @@ -664,7 +657,10 @@ public class VolumeDialog { row.icon.setContentDescription(ss.name); // update slider - updateVolumeRowSliderH(row, zenMuted); + final boolean enableSlider = !zenMuted; + final int vlevel = row.ss.muted && (isRingVibrate || !isRingStream && !zenMuted) ? 0 + : row.ss.level; + updateVolumeRowSliderH(row, enableSlider, vlevel); } private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) { @@ -676,8 +672,8 @@ public class VolumeDialog { row.slider.setThumbTintList(tint); } - private void updateVolumeRowSliderH(VolumeRow row, boolean zenMuted) { - row.slider.setEnabled(!zenMuted); + private void updateVolumeRowSliderH(VolumeRow row, boolean enable, int vlevel) { + row.slider.setEnabled(enable); updateVolumeRowSliderTintH(row, row.stream == mActiveStream); if (row.tracking) { return; // don't update if user is sliding @@ -694,7 +690,6 @@ public class VolumeDialog { row.userAttempt + USER_ATTEMPT_GRACE_PERIOD); return; // don't update if visible and in grace period } - final int vlevel = row.ss.muted ? 0 : row.ss.level; if (vlevel == level) { if (mShowing && rowVisible) { return; // don't clamp if visible @@ -1018,7 +1013,7 @@ public class VolumeDialog { private StreamState ss; private long userAttempt; // last user-driven slider change private boolean tracking; // tracking slider touch - private int requestedLevel; + private int requestedLevel = -1; // pending user-requested level via progress changed private int iconRes; private int iconMuteRes; private boolean important; 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 0000000..6390bcd --- /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 99c4eda..3691a3a 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 e9759c3..4c7b523 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 69405cf..6e6fb7f 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 bc93268..a5b0e59 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 1046b29..0d08c2a 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; @@ -468,12 +472,6 @@ public final class ActivityManagerService extends ActivityManagerNative */ String mDeviceOwnerName; - /** - * Preferred activities to start on boot/user switch, as set by DevicePolicyManager. Indexed - * by userId. - */ - SparseArray<ComponentName> mPreferredSetupActivities = new SparseArray<>(); - public class PendingAssistExtras extends Binder implements Runnable { public final ActivityRecord activity; public final Bundle extras; @@ -3435,20 +3433,13 @@ public final class ActivityManagerService extends ActivityManagerNative // Factory test. ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId); } else { - ComponentName preferredComponent = mPreferredSetupActivities.get(userId); - if (preferredComponent != null) { - ai = AppGlobals.getPackageManager().getActivityInfo( - preferredComponent, flags, userId); - } - if (ai == null) { - ResolveInfo info = AppGlobals.getPackageManager().resolveIntent( - intent, - intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags, userId); + ResolveInfo info = AppGlobals.getPackageManager().resolveIntent( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags, userId); - if (info != null) { - ai = info.activityInfo; - } + if (info != null) { + ai = info.activityInfo; } } } catch (RemoteException e) { @@ -8919,22 +8910,6 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public void updatePreferredSetupActivity(ComponentName preferredActivity, int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { - throw new SecurityException( - "updatePreferredSetupActivity called from non-system process"); - } - synchronized (this) { - if (preferredActivity == null) { - mPreferredSetupActivities.delete(userId); - } else { - mPreferredSetupActivities.put(userId, preferredActivity); - } - } - } - - @Override public void updateDeviceOwner(String packageName) { final int callingUid = Binder.getCallingUid(); if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { @@ -10752,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) { @@ -17781,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; @@ -17901,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) { @@ -17941,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. @@ -18511,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) { @@ -18600,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 = " @@ -18609,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++) { @@ -18620,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 23e62e2..577a4f9 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 cdfcd0c..0e24952 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 14759c3..3acd3a3 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 0000000..5d56d4a --- /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/Installer.java b/services/core/java/com/android/server/pm/Installer.java index fb98d94..ef7be30 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -454,6 +454,18 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + + public int linkFile(String relativePath, String fromBase, String toBase) { + StringBuilder builder = new StringBuilder("linkfile"); + builder.append(' '); + builder.append(relativePath); + builder.append(' '); + builder.append(fromBase); + builder.append(' '); + builder.append(toBase); + return mInstaller.execute(builder.toString()); + } + /** * Returns true iff. {@code instructionSet} is a valid instruction set. */ diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index a1738a2..ca24e3a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -571,6 +571,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IOException("No suitable external storage available"); } + } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { + // For now, installs to adopted media are treated as internal from + // an install flag point-of-view. + params.setInstallFlagsInternal(); + } else { // For now, installs to adopted media are treated as internal from // an install flag point-of-view. diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 1cec750..b5ef3b7 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -166,6 +166,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private final List<File> mResolvedInheritedFiles = new ArrayList<>(); @GuardedBy("mLock") + private final List<String> mResolvedInstructionSets = new ArrayList<>(); + @GuardedBy("mLock") private File mInheritedFilesBase; private final Handler.Callback mHandlerCallback = new Handler.Callback() { @@ -521,7 +523,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } if (isLinkPossible(fromFiles, toDir)) { - createDirsAndLinkFiles(fromFiles, toDir, mInheritedFilesBase); + if (!mResolvedInstructionSets.isEmpty()) { + final File oatDir = new File(toDir, "oat"); + createOatDirs(mResolvedInstructionSets, oatDir); + } + linkFiles(fromFiles, toDir, mInheritedFilesBase); } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. @@ -706,21 +712,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final File oatDir = new File(packageInstallDir, "oat"); if (oatDir.exists()) { final File[] archSubdirs = oatDir.listFiles(); - // Only add "oatDir" if it contains arch specific subdirs. - if (archSubdirs != null && archSubdirs.length > 0) { - mResolvedInheritedFiles.add(oatDir); - } - final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets(); - for (File archSubDir : archSubdirs) { - // Skip any directory that isn't an ISA subdir. - if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) { - continue; - } - List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); - if (!oatFiles.isEmpty()) { - mResolvedInheritedFiles.add(archSubDir); - mResolvedInheritedFiles.addAll(oatFiles); + // Keep track of all instruction sets we've seen compiled output for. + // If we're linking (and not copying) inherited files, we can recreate the + // instruction set hierarchy and link compiled output. + if (archSubdirs != null && archSubdirs.length > 0) { + final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets(); + for (File archSubDir : archSubdirs) { + // Skip any directory that isn't an ISA subdir. + if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) { + continue; + } + + mResolvedInstructionSets.add(archSubDir.getName()); + List<File> oatFiles = Arrays.asList(archSubDir.listFiles()); + if (!oatFiles.isEmpty()) { + mResolvedInheritedFiles.addAll(oatFiles); + } } } } @@ -802,71 +810,41 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return true; } - /** - * Reparents the path of {@code file} from {@code oldBase} to {@code newBase}. {@code file} - * must necessarily be a subpath of {@code oldBase}. It is an error for {@code file} to have - * relative path components such as {@code "."} or {@code ".."}. For example, for we will - * reparent {@code /foo/bar/baz} to {@code /foo2/bar/baz} if {@code oldBase} was {@code /foo} - * and {@code newBase} was {@code /foo2}. - */ - private static File reparentPath(File file, File oldBase, File newBase) throws IOException { - final String oldBaseStr = oldBase.getAbsolutePath(); + private static String getRelativePath(File file, File base) throws IOException { final String pathStr = file.getAbsolutePath(); - + final String baseStr = base.getAbsolutePath(); // Don't allow relative paths. if (pathStr.contains("/.") ) { throw new IOException("Invalid path (was relative) : " + pathStr); } - if (pathStr.startsWith(oldBaseStr)) { - final String relative = pathStr.substring(oldBaseStr.length()); - return new File(newBase, relative); + if (pathStr.startsWith(baseStr)) { + return pathStr.substring(baseStr.length()); } - throw new IOException("File: " + pathStr + " outside base: " + oldBaseStr); + throw new IOException("File: " + pathStr + " outside base: " + baseStr); } - /** - * Recreates a directory and file structure, specified by a list of files {@code fromFiles} - * which are subpaths of {@code fromDir} to {@code toDir}. Directories are created with the - * same permissions, and regular files are linked. - * - * TODO: Move this function to installd so that the system process doesn't have to - * manipulate / relabel directories. - */ - private static void createDirsAndLinkFiles(List<File> fromFiles, File toDir, File fromDir) + private void createOatDirs(List<String> instructionSets, File fromDir) { + for (String instructionSet : instructionSets) { + mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet); + } + } + + private void linkFiles(List<File> fromFiles, File toDir, File fromDir) throws IOException { for (File fromFile : fromFiles) { - final File toFile = reparentPath(fromFile, fromDir, toDir); - final StructStat stat; - try { - stat = Os.stat(fromFile.getAbsolutePath()); - } catch (ErrnoException e) { - throw new IOException("Failed to stat: " + fromFile.getAbsolutePath(), e); - } - - if (OsConstants.S_ISDIR(stat.st_mode)) { - if (LOGD) Slog.d(TAG, "Creating directory " + toFile.getAbsolutePath()); - try { - Os.mkdir(toFile.getAbsolutePath(), stat.st_mode); - } catch (ErrnoException e) { - throw new IOException("Failed to create dir: " + toFile.getAbsolutePath(), e); - } - - // We do this to ensure that the oat/ directory is created with the right - // label (data_dalvikcache_file) instead of apk_tmpfile. - if (!SELinux.restorecon(toFile)) { - throw new IOException("Failed to restorecon: " + toFile.getAbsolutePath()); - } - } else { - if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile); - try { - Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath()); - } catch (ErrnoException e) { - throw new IOException("Failed to link " + fromFile + " to " + toFile, e); - } + final String relativePath = getRelativePath(fromFile, fromDir); + final int ret = mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(), + toDir.getAbsolutePath()); + + if (ret < 0) { + // installd will log failure details. + throw new IOException("failed linkOrCreateDir(" + relativePath + ", " + + fromDir + ", " + toDir + ")"); } } + Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 604ac97..e2cc3f7 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 08d386b..095b7d7 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/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 4b62c40..dd8648d 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -67,6 +67,12 @@ public final class ShutdownThread extends Thread { private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000; private static final int MAX_RADIO_WAIT_TIME = 12*1000; private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000; + // constants for progress bar. the values are roughly estimated based on timeout. + private static final int BROADCAST_STOP_PERCENT = 2; + private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4; + private static final int PACKAGE_MANAGER_STOP_PERCENT = 6; + private static final int RADIO_STOP_PERCENT = 18; + private static final int MOUNT_SERVICE_STOP_PERCENT = 20; // length of vibration before shutting down private static final int SHUTDOWN_VIBRATE_MS = 500; @@ -75,11 +81,13 @@ public final class ShutdownThread extends Thread { private static Object sIsStartedGuard = new Object(); private static boolean sIsStarted = false; - // uncrypt status file + // uncrypt status files private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status"; + private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file"; private static boolean mReboot; private static boolean mRebootSafeMode; + private static boolean mRebootUpdate; private static String mRebootReason; // Provides shutdown assurance in case the system_server is killed @@ -203,6 +211,7 @@ public final class ShutdownThread extends Thread { public static void reboot(final Context context, String reason, boolean confirm) { mReboot = true; mRebootSafeMode = false; + mRebootUpdate = false; mRebootReason = reason; shutdownInner(context, confirm); } @@ -222,6 +231,7 @@ public final class ShutdownThread extends Thread { mReboot = true; mRebootSafeMode = true; + mRebootUpdate = false; mRebootReason = null; shutdownInner(context, confirm); } @@ -235,16 +245,44 @@ public final class ShutdownThread extends Thread { sIsStarted = true; } - // throw up an indeterminate system dialog to indicate radio is - // shutting down. + // Throw up a system dialog to indicate the device is rebooting / shutting down. ProgressDialog pd = new ProgressDialog(context); + + // Path 1: Reboot to recovery and install the update + // Condition: mRebootReason == REBOOT_RECOVERY and mRebootUpdate == True + // (mRebootUpdate is set by checking if /cache/recovery/uncrypt_file exists.) + // UI: progress bar + // + // Path 2: Reboot to recovery for factory reset + // Condition: mRebootReason == REBOOT_RECOVERY + // UI: spinning circle only (no progress bar) + // + // Path 3: Regular reboot / shutdown + // Condition: Otherwise + // UI: spinning circle only (no progress bar) if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) { - pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_recovery_title)); + mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists(); + if (mRebootUpdate) { + pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title)); + pd.setMessage(context.getText( + com.android.internal.R.string.reboot_to_update_prepare)); + pd.setMax(100); + pd.setProgressNumberFormat(null); + pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + pd.setProgress(0); + pd.setIndeterminate(false); + } else { + // Factory reset path. Set the dialog message accordingly. + pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title)); + pd.setMessage(context.getText( + com.android.internal.R.string.reboot_to_reset_message)); + pd.setIndeterminate(true); + } } else { pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); + pd.setIndeterminate(true); } - pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); - pd.setIndeterminate(true); pd.setCancelable(false); pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); @@ -339,13 +377,20 @@ public final class ShutdownThread extends Thread { if (delay <= 0) { Log.w(TAG, "Shutdown broadcast timed out"); break; + } else if (mRebootUpdate) { + int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 * + BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME); + sInstance.setRebootProgress(status, null); } try { - mActionDoneSync.wait(delay); + mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC)); } catch (InterruptedException e) { } } } + if (mRebootUpdate) { + sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null); + } Log.i(TAG, "Shutting down activity manager..."); @@ -357,6 +402,9 @@ public final class ShutdownThread extends Thread { } catch (RemoteException e) { } } + if (mRebootUpdate) { + sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null); + } Log.i(TAG, "Shutting down package manager..."); @@ -365,9 +413,15 @@ public final class ShutdownThread extends Thread { if (pm != null) { pm.shutdown(); } + if (mRebootUpdate) { + sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null); + } // Shutdown radios. shutdownRadios(MAX_RADIO_WAIT_TIME); + if (mRebootUpdate) { + sInstance.setRebootProgress(RADIO_STOP_PERCENT, null); + } // Shutdown MountService to ensure media is in a safe state IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { @@ -399,64 +453,44 @@ public final class ShutdownThread extends Thread { if (delay <= 0) { Log.w(TAG, "Shutdown wait timed out"); break; + } else if (mRebootUpdate) { + int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 * + (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) / + MAX_SHUTDOWN_WAIT_TIME); + status += RADIO_STOP_PERCENT; + sInstance.setRebootProgress(status, null); } try { - mActionDoneSync.wait(delay); + mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC)); } catch (InterruptedException e) { } } } + if (mRebootUpdate) { + sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null); - // If it's to reboot into recovery, invoke uncrypt via init service. - if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) { + // If it's to reboot to install update, invoke uncrypt via init service. uncrypt(); } rebootOrShutdown(mContext, mReboot, mRebootReason); } - private void prepareUncryptProgress() { - // Reset the dialog message to show the decrypt process. - mHandler.post(new Runnable() { - @Override - public void run() { - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - } - // It doesn't work to change the style of the existing - // one. Have to create a new one. - ProgressDialog pd = new ProgressDialog(mContext); - - pd.setTitle(mContext.getText( - com.android.internal.R.string.reboot_to_recovery_title)); - pd.setMessage(mContext.getText( - com.android.internal.R.string.reboot_to_recovery_progress)); - pd.setIndeterminate(false); - pd.setMax(100); - pd.setCancelable(false); - pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - pd.setProgressNumberFormat(null); - pd.setProgress(0); - - mProgressDialog = pd; - mProgressDialog.show(); - } - }); - } - - private void setUncryptProgress(final int progress) { + private void setRebootProgress(final int progress, final CharSequence message) { mHandler.post(new Runnable() { @Override public void run() { if (mProgressDialog != null) { mProgressDialog.setProgress(progress); + if (message != null) { + mProgressDialog.setMessage(message); + } } } }); } - private void shutdownRadios(int timeout) { + private void shutdownRadios(final int timeout) { // If a radio is wedged, disabling it may hang so we do this work in another thread, // just in case. final long endTime = SystemClock.elapsedRealtime() + timeout; @@ -511,7 +545,15 @@ public final class ShutdownThread extends Thread { Log.i(TAG, "Waiting for NFC, Bluetooth and Radio..."); - while (SystemClock.elapsedRealtime() < endTime) { + long delay = endTime - SystemClock.elapsedRealtime(); + while (delay > 0) { + if (mRebootUpdate) { + int status = (int)((timeout - delay) * 1.0 * + (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout); + status += PACKAGE_MANAGER_STOP_PERCENT; + sInstance.setRebootProgress(status, null); + } + if (!bluetoothOff) { try { bluetoothOff = !bluetooth.isEnabled(); @@ -552,6 +594,8 @@ public final class ShutdownThread extends Thread { break; } SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC); + + delay = endTime - SystemClock.elapsedRealtime(); } } }; @@ -604,9 +648,6 @@ public final class ShutdownThread extends Thread { private void uncrypt() { Log.i(TAG, "Calling uncrypt and monitoring the progress..."); - // Update the ProcessDialog message and style. - sInstance.prepareUncryptProgress(); - final boolean[] done = new boolean[1]; done[0] = false; Thread t = new Thread() { @@ -627,25 +668,32 @@ public final class ShutdownThread extends Thread { try (BufferedReader reader = new BufferedReader( new FileReader(UNCRYPT_STATUS_FILE))) { - int last_status = Integer.MIN_VALUE; + int lastStatus = Integer.MIN_VALUE; while (true) { String str = reader.readLine(); try { int status = Integer.parseInt(str); // Avoid flooding the log with the same message. - if (status == last_status && last_status != Integer.MIN_VALUE) { + if (status == lastStatus && lastStatus != Integer.MIN_VALUE) { continue; } - last_status = status; + lastStatus = status; if (status >= 0 && status < 100) { // Update status Log.d(TAG, "uncrypt read status: " + status); - sInstance.setUncryptProgress(status); + // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100). + status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100); + status += MOUNT_SERVICE_STOP_PERCENT; + CharSequence msg = mContext.getText( + com.android.internal.R.string.reboot_to_update_package); + sInstance.setRebootProgress(status, msg); } else if (status == 100) { Log.d(TAG, "uncrypt successfully finished."); - sInstance.setUncryptProgress(status); + CharSequence msg = mContext.getText( + com.android.internal.R.string.reboot_to_update_reboot); + sInstance.setRebootProgress(status, msg); break; } else { // Error in /system/bin/uncrypt. Or it's rebooting to recovery diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 38e2765..4861050 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 b42c8eb..5064d8f 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/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp index cb70144..06b9bc3 100644 --- a/services/core/jni/com_android_server_UsbMidiDevice.cpp +++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp @@ -36,6 +36,7 @@ namespace android { static jclass sFileDescriptorClass; +static jfieldID sPipeFDField; static jint android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, @@ -66,14 +67,15 @@ android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */ } static jobjectArray -android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device, +android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, jint device, jint subdevice_count) { char path[100]; snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device); - jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL); + // allocate one extra file descriptor for close pipe + jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL); if (!fds) { return NULL; } @@ -91,12 +93,27 @@ android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, ji env->DeleteLocalRef(fileDescriptor); } + // create a pipe to use for unblocking our input thread + int pipeFD[2]; + pipe(pipeFD); + jobject fileDescriptor = jniCreateFileDescriptor(env, pipeFD[0]); + env->SetObjectArrayElement(fds, subdevice_count, fileDescriptor); + env->DeleteLocalRef(fileDescriptor); + // store our end of the pipe in mPipeFD + env->SetIntField(thiz, sPipeFDField, pipeFD[1]); + return fds; } static void -android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds) +android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) { + // write to mPipeFD to unblock input thread + jint pipeFD = env->GetIntField(thiz, sPipeFDField); + write(pipeFD, &pipeFD, sizeof(pipeFD)); + close(pipeFD); + env->SetIntField(thiz, sPipeFDField, -1); + int count = env->GetArrayLength(fds); for (int i = 0; i < count; i++) { jobject fd = env->GetObjectArrayElement(fds, i); @@ -117,13 +134,18 @@ int register_android_server_UsbMidiDevice(JNIEnv *env) ALOGE("Can't find java/io/FileDescriptor"); return -1; } - sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);; + sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz); clazz = env->FindClass("com/android/server/usb/UsbMidiDevice"); if (clazz == NULL) { ALOGE("Can't find com/android/server/usb/UsbMidiDevice"); return -1; } + sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I"); + if (sPipeFDField == NULL) { + ALOGE("Can't find UsbMidiDevice.mPipeFD"); + return -1; + } return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice", method_table, NELEM(method_table)); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 825ef1a..feb0285 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -183,7 +183,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; - private static final String ATTR_PREFERRED_SETUP_ACTIVITY = "setup-activity"; private static final String ATTR_PERMISSION_POLICY = "permission-policy"; private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer"; @@ -335,8 +334,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean doNotAskCredentialsOnBoot = false; - ComponentName mPreferredSetupActivity; - public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } @@ -1436,12 +1433,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER, policy.mDelegatedCertInstallerPackage); } - if (policy.mPreferredSetupActivity != null) { - out.attribute(null, ATTR_PREFERRED_SETUP_ACTIVITY, - policy.mPreferredSetupActivity.flattenToString()); - } else { - out.attribute(null, ATTR_PREFERRED_SETUP_ACTIVITY, ""); - } final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { @@ -1566,12 +1557,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null, ATTR_DELEGATED_CERT_INSTALLER); - String preferredSetupActivity = - parser.getAttributeValue(null, ATTR_PREFERRED_SETUP_ACTIVITY); - if (preferredSetupActivity != null) { - policy.mPreferredSetupActivity = - ComponentName.unflattenFromString(preferredSetupActivity); - } type = parser.next(); int outerDepth = parser.getDepth(); @@ -1695,7 +1680,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (policy.mStatusBarDisabled) { setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle); } - updatePreferredSetupActivityLocked(userHandle); } private void updateLockTaskPackagesLocked(List<String> packages, int userId) { @@ -4734,43 +4718,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setPreferredSetupActivity(ComponentName who, ComponentName activity) { - if (!mHasFeature) { - return; - } - Preconditions.checkNotNull(who, "ComponentName is null"); - synchronized (this) { - ActiveAdmin activeAdmin = - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); - if (!isDeviceInitializer(activeAdmin.info.getPackageName())) { - throw new SecurityException( - "This method can only be called by device initializers"); - } - int userHandle = UserHandle.getCallingUserId(); - DevicePolicyData userData = getUserData(userHandle); - userData.mPreferredSetupActivity = activity; - saveSettingsLocked(userHandle); - updatePreferredSetupActivityLocked(userHandle); - } - } - - private void updatePreferredSetupActivityLocked(int userHandle) { - if (!mHasFeature) { - return; - } - IActivityManager am = ActivityManagerNative.getDefault(); - long ident = Binder.clearCallingIdentity(); - try { - am.updatePreferredSetupActivity( - getUserData(userHandle).mPreferredSetupActivity, userHandle); - } catch (RemoteException e) { - // Not gonna happen. - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) { Preconditions.checkNotNull(who, "ComponentName is null"); final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); @@ -6138,9 +6085,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!policy.mUserSetupComplete) { policy.mUserSetupComplete = true; synchronized (this) { - // Clear the preferred setup activity. - policy.mPreferredSetupActivity = null; - updatePreferredSetupActivityLocked(userHandle); // The DeviceInitializer was whitelisted but now should be removed. removeDeviceInitializerFromLockTaskPackages(userHandle); saveSettingsLocked(userHandle); diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index 671cf01..97bf505 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -19,6 +19,7 @@ package com.android.server.usb; import android.content.Context; import android.media.midi.MidiDeviceInfo; import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiDeviceStatus; import android.media.midi.MidiManager; import android.media.midi.MidiReceiver; import android.media.midi.MidiSender; @@ -43,38 +44,100 @@ import java.io.IOException; public final class UsbMidiDevice implements Closeable { private static final String TAG = "UsbMidiDevice"; + private final int mAlsaCard; + private final int mAlsaDevice; + private final int mSubdeviceCount; + private final InputReceiverProxy[] mInputPortReceivers; + private MidiDeviceServer mServer; // event schedulers for each output port - private final MidiEventScheduler[] mEventSchedulers; + private MidiEventScheduler[] mEventSchedulers; private static final int BUFFER_SIZE = 512; - private final FileDescriptor[] mFileDescriptors; + private FileDescriptor[] mFileDescriptors; // for polling multiple FileDescriptors for MIDI events - private final StructPollfd[] mPollFDs; + private StructPollfd[] mPollFDs; // streams for reading from ALSA driver - private final FileInputStream[] mInputStreams; + private FileInputStream[] mInputStreams; // streams for writing to ALSA driver - private final FileOutputStream[] mOutputStreams; + private FileOutputStream[] mOutputStreams; - public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { - // FIXME - support devices with different number of input and output ports - int subDevices = nativeGetSubdeviceCount(card, device); - if (subDevices <= 0) { - Log.e(TAG, "nativeGetSubdeviceCount failed"); - return null; + private final Object mLock = new Object(); + private boolean mIsOpen; + + // pipe file descriptor for signalling input thread to exit + // only accessed from JNI code + private int mPipeFD = -1; + + private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { + + @Override + public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { + MidiDeviceInfo deviceInfo = status.getDeviceInfo(); + int inputPorts = deviceInfo.getInputPortCount(); + int outputPorts = deviceInfo.getOutputPortCount(); + boolean hasOpenPorts = false; + + for (int i = 0; i < inputPorts; i++) { + if (status.isInputPortOpen(i)) { + hasOpenPorts = true; + break; + } + } + + if (!hasOpenPorts) { + for (int i = 0; i < outputPorts; i++) { + if (status.getOutputPortOpenCount(i) > 0) { + hasOpenPorts = true; + break; + } + } + } + + synchronized (mLock) { + if (hasOpenPorts && !mIsOpen) { + openLocked(); + } else if (!hasOpenPorts && mIsOpen) { + closeLocked(); + } + } + } + + @Override + public void onClose() { + } + }; + + // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist + // until the device has active clients + private final class InputReceiverProxy extends MidiReceiver { + private MidiReceiver mReceiver; + + @Override + public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { + MidiReceiver receiver = mReceiver; + if (receiver != null) { + receiver.send(msg, offset, count, timestamp); + } } + public void setReceiver(MidiReceiver receiver) { + mReceiver = receiver; + } + } + + public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { // FIXME - support devices with different number of input and output ports - FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices); - if (fileDescriptors == null) { - Log.e(TAG, "nativeOpen failed"); + int subDeviceCount = nativeGetSubdeviceCount(card, device); + if (subDeviceCount <= 0) { + Log.e(TAG, "nativeGetSubdeviceCount failed"); return null; } - UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors); + UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); if (!midiDevice.register(context, properties)) { IoUtils.closeQuietly(midiDevice); Log.e(TAG, "createDeviceServer failed"); @@ -83,10 +146,32 @@ public final class UsbMidiDevice implements Closeable { return midiDevice; } - private UsbMidiDevice(FileDescriptor[] fileDescriptors) { + private UsbMidiDevice(int card, int device, int subdeviceCount) { + mAlsaCard = card; + mAlsaDevice = device; + mSubdeviceCount = subdeviceCount; + + // FIXME - support devices with different number of input and output ports + int inputCount = subdeviceCount; + mInputPortReceivers = new InputReceiverProxy[inputCount]; + for (int port = 0; port < inputCount; port++) { + mInputPortReceivers[port] = new InputReceiverProxy(); + } + } + + private boolean openLocked() { + // FIXME - support devices with different number of input and output ports + FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); + if (fileDescriptors == null) { + Log.e(TAG, "nativeOpen failed"); + return false; + } + mFileDescriptors = fileDescriptors; int inputCount = fileDescriptors.length; - int outputCount = fileDescriptors.length; + // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() + // in our input thread + int outputCount = fileDescriptors.length - 1; mPollFDs = new StructPollfd[inputCount]; mInputStreams = new FileInputStream[inputCount]; @@ -103,29 +188,12 @@ public final class UsbMidiDevice implements Closeable { mEventSchedulers = new MidiEventScheduler[outputCount]; for (int i = 0; i < outputCount; i++) { mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); - mEventSchedulers[i] = new MidiEventScheduler(); - } - } - private boolean register(Context context, Bundle properties) { - MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); - if (midiManager == null) { - Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); - return false; + MidiEventScheduler scheduler = new MidiEventScheduler(); + mEventSchedulers[i] = scheduler; + mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); } - int inputCount = mInputStreams.length; - int outputCount = mOutputStreams.length; - MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount]; - for (int port = 0; port < inputCount; port++) { - inputPortReceivers[port] = mEventSchedulers[port].getReceiver(); - } - - mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount, - null, null, properties, MidiDeviceInfo.TYPE_USB, null); - if (mServer == null) { - return false; - } final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); // Create input thread which will read from all input ports @@ -134,24 +202,32 @@ public final class UsbMidiDevice implements Closeable { public void run() { byte[] buffer = new byte[BUFFER_SIZE]; try { - boolean done = false; - while (!done) { - // look for a readable FileDescriptor - for (int index = 0; index < mPollFDs.length; index++) { - StructPollfd pfd = mPollFDs[index]; - if ((pfd.revents & OsConstants.POLLIN) != 0) { - // clear readable flag - pfd.revents = 0; - - int count = mInputStreams[index].read(buffer); - outputReceivers[index].send(buffer, 0, count); - } else if ((pfd.revents & (OsConstants.POLLERR - | OsConstants.POLLHUP)) != 0) { - done = true; + while (true) { + synchronized (mLock) { + if (!mIsOpen) break; + + // look for a readable FileDescriptor + for (int index = 0; index < mPollFDs.length; index++) { + StructPollfd pfd = mPollFDs[index]; + if ((pfd.revents & (OsConstants.POLLERR + | OsConstants.POLLHUP)) != 0) { + break; + } else if ((pfd.revents & OsConstants.POLLIN) != 0) { + // clear readable flag + pfd.revents = 0; + + if (index == mInputStreams.length - 1) { + // last file descriptor is used only for unblocking Os.poll() + break; + } + + int count = mInputStreams[index].read(buffer); + outputReceivers[index].send(buffer, 0, count); + } } } - // wait until we have a readable port + // wait until we have a readable port or we are signalled to close Os.poll(mPollFDs, -1 /* infinite timeout */); } } catch (IOException e) { @@ -195,29 +271,64 @@ public final class UsbMidiDevice implements Closeable { }.start(); } + mIsOpen = true; + return true; + } + + private boolean register(Context context, Bundle properties) { + MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); + if (midiManager == null) { + Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); + return false; + } + + mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, + null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); + if (mServer == null) { + return false; + } + return true; } @Override public void close() throws IOException { - for (int i = 0; i < mEventSchedulers.length; i++) { - mEventSchedulers[i].close(); + synchronized (mLock) { + if (mIsOpen) { + closeLocked(); + } } if (mServer != null) { - mServer.close(); + IoUtils.closeQuietly(mServer); + } + } + + private void closeLocked() { + for (int i = 0; i < mEventSchedulers.length; i++) { + mInputPortReceivers[i].setReceiver(null); + mEventSchedulers[i].close(); } + mEventSchedulers = null; for (int i = 0; i < mInputStreams.length; i++) { - mInputStreams[i].close(); + IoUtils.closeQuietly(mInputStreams[i]); } + mInputStreams = null; + for (int i = 0; i < mOutputStreams.length; i++) { - mOutputStreams[i].close(); + IoUtils.closeQuietly(mOutputStreams[i]); } + mOutputStreams = null; + + // nativeClose will close the file descriptors and signal the input thread to exit nativeClose(mFileDescriptors); + mFileDescriptors = null; + + mIsOpen = false; } private static native int nativeGetSubdeviceCount(int card, int device); - private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); - private static native void nativeClose(FileDescriptor[] fileDescriptors); + private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); + private native void nativeClose(FileDescriptor[] fileDescriptors); } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index bb210f1..0042414 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 f7f4425..fb985ce 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 d62c08e..8f7b82f 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 fb0ecb0..368e137 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 11a4976..dabf706 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 d192288..16472c8 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 1c69794..b0b95f6 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 dae1ac3..3090a11 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 67db289..c038414 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 5d212a4..a7636c3 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 d311cd9..10f8150 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 de2dafc..41c229d 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 0000000..e89fb7c --- /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 0000000..bbb3e64 --- /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 10c75aa..3606488 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 7adaf1e..03ecd1a 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 6015d82..9cdb152 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 c16f6bb..0b08d24 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 c017a0d..8533c28 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 ce5201b..3387135 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 77ccedb..50a51d9 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" |