diff options
196 files changed, 5259 insertions, 1903 deletions
diff --git a/Android.mk b/Android.mk index ad164e207b9b..d68da97451c5 100644 --- a/Android.mk +++ b/Android.mk @@ -136,6 +136,7 @@ LOCAL_SRC_FILES += \ core/java/android/content/ISyncStatusObserver.aidl \ core/java/android/content/pm/ILauncherApps.aidl \ core/java/android/content/pm/IOnAppsChangedListener.aidl \ + core/java/android/content/pm/IOtaDexopt.aidl \ core/java/android/content/pm/IPackageDataObserver.aidl \ core/java/android/content/pm/IPackageDeleteObserver.aidl \ core/java/android/content/pm/IPackageDeleteObserver2.aidl \ diff --git a/api/current.txt b/api/current.txt index c7f0a75dcc53..18a1582167f7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -68,7 +68,7 @@ package android { field public static final java.lang.String DUMP = "android.permission.DUMP"; field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR"; field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST"; - field public static final deprecated java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; + field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE"; field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS"; @@ -4422,9 +4422,11 @@ package android.app { method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle); method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle); method public void onLowMemory(); + method public void onMultiWindowChanged(boolean); method public boolean onOptionsItemSelected(android.view.MenuItem); method public void onOptionsMenuClosed(android.view.Menu); method public void onPause(); + method public void onPictureInPictureChanged(boolean); method public void onPrepareOptionsMenu(android.view.Menu); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method public void onResume(); @@ -4505,9 +4507,11 @@ package android.app { method public void dispatchDestroy(); method public void dispatchDestroyView(); method public void dispatchLowMemory(); + method public void dispatchMultiWindowChanged(boolean); method public boolean dispatchOptionsItemSelected(android.view.MenuItem); method public void dispatchOptionsMenuClosed(android.view.Menu); method public void dispatchPause(); + method public void dispatchPictureInPictureChanged(boolean); method public boolean dispatchPrepareOptionsMenu(android.view.Menu); method public void dispatchResume(); method public void dispatchStart(); @@ -23390,6 +23394,7 @@ package android.net { method public void unregisterNetworkCallback(android.app.PendingIntent); field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + field public static final java.lang.String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; @@ -30136,6 +30141,7 @@ package android.provider { } public class BlockedNumberContract { + method public static boolean canCurrentUserBlockNumbers(android.content.Context); method public static boolean isBlocked(android.content.Context, java.lang.String); field public static final java.lang.String AUTHORITY = "com.android.blockednumber"; field public static final android.net.Uri AUTHORITY_URI; @@ -30145,7 +30151,6 @@ package android.provider { field public static final java.lang.String COLUMN_E164_NUMBER = "e164_number"; field public static final java.lang.String COLUMN_ID = "_id"; field public static final java.lang.String COLUMN_ORIGINAL_NUMBER = "original_number"; - field public static final java.lang.String COLUMN_STRIPPED_NUMBER = "stripped_number"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_numbers"; field public static final android.net.Uri CONTENT_URI; @@ -43689,6 +43694,7 @@ package android.view.accessibility { method public boolean isEnabled(); method public boolean isFocusable(); method public boolean isFocused(); + method public boolean isImportantForAccessibility(); method public boolean isLongClickable(); method public boolean isMultiLine(); method public boolean isPassword(); @@ -43727,6 +43733,7 @@ package android.view.accessibility { method public void setError(java.lang.CharSequence); method public void setFocusable(boolean); method public void setFocused(boolean); + method public void setImportantForAccessibility(boolean); method public void setInputType(int); method public void setLabelFor(android.view.View); method public void setLabelFor(android.view.View, int); @@ -52537,7 +52544,6 @@ package java.net { method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory); method public java.lang.String toExternalForm(); method public java.net.URI toURI() throws java.net.URISyntaxException; - method public java.net.URI toURILenient() throws java.net.URISyntaxException; } public class URLClassLoader extends java.security.SecureClassLoader implements java.io.Closeable { diff --git a/api/system-current.txt b/api/system-current.txt index 3c5e59e6a4c3..7c181c80fd9e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -96,7 +96,7 @@ package android { field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST"; field public static final java.lang.String FORCE_BACK = "android.permission.FORCE_BACK"; field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; - field public static final deprecated java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; + field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE"; @@ -293,6 +293,7 @@ package android { public static final class R.attr { ctor public R.attr(); + field public static final int abiOverride = 16844054; // 0x1010516 field public static final int absListViewStyle = 16842858; // 0x101006a field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 @@ -4554,9 +4555,11 @@ package android.app { method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle); method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle); method public void onLowMemory(); + method public void onMultiWindowChanged(boolean); method public boolean onOptionsItemSelected(android.view.MenuItem); method public void onOptionsMenuClosed(android.view.Menu); method public void onPause(); + method public void onPictureInPictureChanged(boolean); method public void onPrepareOptionsMenu(android.view.Menu); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method public void onResume(); @@ -4637,9 +4640,11 @@ package android.app { method public void dispatchDestroy(); method public void dispatchDestroyView(); method public void dispatchLowMemory(); + method public void dispatchMultiWindowChanged(boolean); method public boolean dispatchOptionsItemSelected(android.view.MenuItem); method public void dispatchOptionsMenuClosed(android.view.Menu); method public void dispatchPause(); + method public void dispatchPictureInPictureChanged(boolean); method public boolean dispatchPrepareOptionsMenu(android.view.Menu); method public void dispatchResume(); method public void dispatchStart(); @@ -24957,6 +24962,7 @@ package android.net { method public void unregisterNetworkCallback(android.app.PendingIntent); field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + field public static final java.lang.String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; @@ -32183,6 +32189,7 @@ package android.provider { } public class BlockedNumberContract { + method public static boolean canCurrentUserBlockNumbers(android.content.Context); method public static boolean isBlocked(android.content.Context, java.lang.String); field public static final java.lang.String AUTHORITY = "com.android.blockednumber"; field public static final android.net.Uri AUTHORITY_URI; @@ -32192,7 +32199,6 @@ package android.provider { field public static final java.lang.String COLUMN_E164_NUMBER = "e164_number"; field public static final java.lang.String COLUMN_ID = "_id"; field public static final java.lang.String COLUMN_ORIGINAL_NUMBER = "original_number"; - field public static final java.lang.String COLUMN_STRIPPED_NUMBER = "stripped_number"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_numbers"; field public static final android.net.Uri CONTENT_URI; @@ -46110,6 +46116,7 @@ package android.view.accessibility { method public boolean isEnabled(); method public boolean isFocusable(); method public boolean isFocused(); + method public boolean isImportantForAccessibility(); method public boolean isLongClickable(); method public boolean isMultiLine(); method public boolean isPassword(); @@ -46148,6 +46155,7 @@ package android.view.accessibility { method public void setError(java.lang.CharSequence); method public void setFocusable(boolean); method public void setFocused(boolean); + method public void setImportantForAccessibility(boolean); method public void setInputType(int); method public void setLabelFor(android.view.View); method public void setLabelFor(android.view.View, int); @@ -55291,7 +55299,6 @@ package java.net { method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory); method public java.lang.String toExternalForm(); method public java.net.URI toURI() throws java.net.URISyntaxException; - method public java.net.URI toURILenient() throws java.net.URISyntaxException; } public class URLClassLoader extends java.security.SecureClassLoader implements java.io.Closeable { diff --git a/api/test-current.txt b/api/test-current.txt index 3e755ae299c7..8551950c1c9e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -68,7 +68,7 @@ package android { field public static final java.lang.String DUMP = "android.permission.DUMP"; field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR"; field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST"; - field public static final deprecated java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; + field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS"; field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED"; field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE"; field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS"; @@ -4422,9 +4422,11 @@ package android.app { method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle); method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle); method public void onLowMemory(); + method public void onMultiWindowChanged(boolean); method public boolean onOptionsItemSelected(android.view.MenuItem); method public void onOptionsMenuClosed(android.view.Menu); method public void onPause(); + method public void onPictureInPictureChanged(boolean); method public void onPrepareOptionsMenu(android.view.Menu); method public void onRequestPermissionsResult(int, java.lang.String[], int[]); method public void onResume(); @@ -4505,9 +4507,11 @@ package android.app { method public void dispatchDestroy(); method public void dispatchDestroyView(); method public void dispatchLowMemory(); + method public void dispatchMultiWindowChanged(boolean); method public boolean dispatchOptionsItemSelected(android.view.MenuItem); method public void dispatchOptionsMenuClosed(android.view.Menu); method public void dispatchPause(); + method public void dispatchPictureInPictureChanged(boolean); method public boolean dispatchPrepareOptionsMenu(android.view.Menu); method public void dispatchResume(); method public void dispatchStart(); @@ -23399,6 +23403,7 @@ package android.net { method public void unregisterNetworkCallback(android.app.PendingIntent); field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + field public static final java.lang.String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; @@ -30149,6 +30154,7 @@ package android.provider { } public class BlockedNumberContract { + method public static boolean canCurrentUserBlockNumbers(android.content.Context); method public static boolean isBlocked(android.content.Context, java.lang.String); field public static final java.lang.String AUTHORITY = "com.android.blockednumber"; field public static final android.net.Uri AUTHORITY_URI; @@ -30158,7 +30164,6 @@ package android.provider { field public static final java.lang.String COLUMN_E164_NUMBER = "e164_number"; field public static final java.lang.String COLUMN_ID = "_id"; field public static final java.lang.String COLUMN_ORIGINAL_NUMBER = "original_number"; - field public static final java.lang.String COLUMN_STRIPPED_NUMBER = "stripped_number"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_numbers"; field public static final android.net.Uri CONTENT_URI; @@ -43706,6 +43711,7 @@ package android.view.accessibility { method public boolean isEnabled(); method public boolean isFocusable(); method public boolean isFocused(); + method public boolean isImportantForAccessibility(); method public boolean isLongClickable(); method public boolean isMultiLine(); method public boolean isPassword(); @@ -43744,6 +43750,7 @@ package android.view.accessibility { method public void setError(java.lang.CharSequence); method public void setFocusable(boolean); method public void setFocused(boolean); + method public void setImportantForAccessibility(boolean); method public void setInputType(int); method public void setLabelFor(android.view.View); method public void setLabelFor(android.view.View, int); @@ -52554,7 +52561,6 @@ package java.net { method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory); method public java.lang.String toExternalForm(); method public java.net.URI toURI() throws java.net.URISyntaxException; - method public java.net.URI toURILenient() throws java.net.URISyntaxException; } public class URLClassLoader extends java.security.SecureClassLoader implements java.io.Closeable { diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index ba93b2a2cafe..9d81c438360f 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -21,10 +21,12 @@ package com.android.commands.am; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.RESIZE_MODE_SYSTEM; import static android.app.ActivityManager.RESIZE_MODE_USER; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import android.app.ActivityManager; import android.app.ActivityManager.StackInfo; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.IActivityContainer; import android.app.IActivityController; import android.app.IActivityManager; @@ -106,6 +108,7 @@ public class Am extends BaseCommand { private String mProfileFile; private int mSamplingInterval; private boolean mAutoStop; + private int mStackId; /** * Command-line entry point. @@ -191,6 +194,7 @@ public class Am extends BaseCommand { " --track-allocation: enable tracking of object allocations\n" + " --user <USER_ID> | current: Specify which user to run as; if not\n" + " specified then run as the current user.\n" + + " --stack <STACK_ID>: Specify into which stack should the activity be put." + "\n" + "am startservice: start a Service. Options are:\n" + " --user <USER_ID> | current: Specify which user to run as; if not\n" + @@ -467,6 +471,7 @@ public class Am extends BaseCommand { mSamplingInterval = 0; mAutoStop = false; mUserId = defUser; + mStackId = FULLSCREEN_WORKSPACE_STACK_ID; return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() { @Override @@ -493,6 +498,8 @@ public class Am extends BaseCommand { mUserId = parseUserArg(nextArgRequired()); } else if (opt.equals("--receiver-permission")) { mReceiverPermission = nextArgRequired(); + } else if (opt.equals("--stack")) { + mStackId = Integer.parseInt(nextArgRequired()); } else { return false; } @@ -550,6 +557,7 @@ public class Am extends BaseCommand { mimeType = mAm.getProviderMimeType(intent.getData(), mUserId); } + do { if (mStopOption) { String packageName; @@ -604,13 +612,20 @@ public class Am extends BaseCommand { IActivityManager.WaitResult result = null; int res; final long startTime = SystemClock.uptimeMillis(); + ActivityOptions options = null; + if (mStackId != FULLSCREEN_WORKSPACE_STACK_ID) { + options = ActivityOptions.makeBasic(); + options.setLaunchStackId(mStackId); + } if (mWaitOption) { result = mAm.startActivityAndWait(null, null, intent, mimeType, - null, null, 0, mStartFlags, profilerInfo, null, mUserId); + null, null, 0, mStartFlags, profilerInfo, + options != null ? options.toBundle() : null, mUserId); res = result.result; } else { res = mAm.startActivityAsUser(null, null, intent, mimeType, - null, null, 0, mStartFlags, profilerInfo, null, mUserId); + null, null, 0, mStartFlags, profilerInfo, + options != null ? options.toBundle() : null, mUserId); } final long endTime = SystemClock.uptimeMillis(); PrintStream out = mWaitOption ? System.out : System.err; diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 35695c47d19b..1d9e3bb4b4bb 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -430,46 +430,47 @@ public class AccountManager { } /** - * List every {@link Account} registered on the device that are managed by - * applications whose signatures match the caller. + * Lists all accounts of any type registered on the device. + * Equivalent to getAccountsByType(null). * - * <p>This method can be called safely from the main thread. It is - * equivalent to calling <code>getAccountsByType(null)</code>. + * <p>It is safe to call this method from the main thread. * - * <p><b>NOTE:</b> Apps declaring a {@code targetSdkVersion<=23} in their - * manifests will continue to behave as they did on devices that support - * API level 23. In particular the GET_ACCOUNTS permission is required to - * see all the Accounts registered with the AccountManager. See docs for - * this function in API level 23 for more information. + * <p>Clients of this method that have not been granted the + * {@link android.Manifest.permission#GET_ACCOUNTS} permission, + * will only see those accounts managed by AbstractAccountAuthenticators whose + * signature matches the client. * - * @return Array of Accounts. The array may be empty if no accounts are - * available to the caller. + * @return An array of {@link Account}, one for each account. Empty + * (never null) if no accounts have been added. */ @NonNull + @RequiresPermission(GET_ACCOUNTS) public Account[] getAccounts() { - return getAccountsByType(null); + try { + return mService.getAccounts(null, mContext.getOpPackageName()); + } catch (RemoteException e) { + // won't ever happen + throw new RuntimeException(e); + } } /** * @hide - * List every {@link Account} registered on the device for a specific User - * that are managed by applications whose signatures match the caller. + * Lists all accounts of any type registered on the device for a given + * user id. Equivalent to getAccountsByType(null). * - * <p><b>NOTE:</b> Apps declaring a {@code targetSdkVersion<=23} in their - * manifests will continue to behave as they did on devices that support - * API level 23. In particular the GET_ACCOUNTS permission is required to - * see all the Accounts registered with the AccountManager for the - * specified userId. See docs for this function in API level 23 for more - * information. + * <p>It is safe to call this method from the main thread. * - * <p>This method can be called safely from the main thread. + * <p>Clients of this method that have not been granted the + * {@link android.Manifest.permission#GET_ACCOUNTS} permission, + * will only see those accounts managed by AbstractAccountAuthenticators whose + * signature matches the client. * - * @param int userId associated with the User whose accounts should be - * queried. - * @return Array of Accounts. The array may be empty if no accounts are - * available to the caller. + * @return An array of {@link Account}, one for each account. Empty + * (never null) if no accounts have been added. */ @NonNull + @RequiresPermission(GET_ACCOUNTS) public Account[] getAccountsAsUser(int userId) { try { return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName()); @@ -500,11 +501,10 @@ public class AccountManager { /** * Returns the accounts visible to the specified package, in an environment where some apps * are not authorized to view all accounts. This method can only be called by system apps. - * * @param type The type of accounts to return, null to retrieve all accounts * @param packageName The package name of the app for which the accounts are to be returned - * @return Array of Accounts. The array may be empty if no accounts of th - * specified type are visible to the caller. + * @return An array of {@link Account}, one per matching account. Empty + * (never null) if no accounts of the specified type have been added. */ @NonNull public Account[] getAccountsByTypeForPackage(String type, String packageName) { @@ -518,22 +518,29 @@ public class AccountManager { } /** - * List every {@link Account} of a specified type managed by applications - * whose signatures match the caller. + * Lists all accounts of a particular type. The account type is a + * string token corresponding to the authenticator and useful domain + * of the account. For example, there are types corresponding to Google + * and Facebook. The exact string token to use will be published somewhere + * associated with the authenticator in question. * - * <p><b>NOTE:</b> Apps declaring a {@code targetSdkVersion<=23} in their - * manifests will continue to behave as they did on devices that support - * API level 23. See docs for this function in API level 23 for more - * information. + * <p>It is safe to call this method from the main thread. + * + * <p>Clients of this method that have not been granted the + * {@link android.Manifest.permission#GET_ACCOUNTS} permission, + * will only see those accounts managed by AbstractAccountAuthenticators whose + * signature matches the client. * - * <p>This method can be called safely from the main thread. + * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before, + * GET_ACCOUNTS permission is needed for those platforms, irrespective of uid + * or signature match. See docs for this function in API level 22. * - * @param type String denoting the type of the accounts to return, - * {@code null} to retrieve all accounts visible to the caller. - * @return An array of Accounts. Empty (never null) if no accounts - * are available to the caller. + * @param type The type of accounts to return, null to retrieve all accounts + * @return An array of {@link Account}, one per matching account. Empty + * (never null) if no accounts of the specified type have been added. */ @NonNull + @RequiresPermission(GET_ACCOUNTS) public Account[] getAccountsByType(String type) { return getAccountsByTypeAsUser(type, Process.myUserHandle()); } @@ -579,7 +586,6 @@ public class AccountManager { * @return a future containing the label string * @hide */ - @NonNull public AccountManagerFuture<String> getAuthTokenLabel( final String accountType, final String authTokenType, AccountManagerCallback<String> callback, Handler handler) { @@ -612,13 +618,9 @@ public class AccountManager { * <p>This method may be called from any thread, but the returned * {@link AccountManagerFuture} must not be used on the main thread. * - * <p><b>Note:</b>The specified account must be managed by an application - * whose signature matches the caller. - * - * <p><b>Further note:</b>Apps targeting API level 23 or earlier will continue to - * behave as they did on devices that support API level 23. In particular - * they may still require the GET_ACCOUNTS permission. See docs for this - * function in API level 23. + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#GET_ACCOUNTS} or be a signature + * match with the AbstractAccountAuthenticator that manages the account. * * @param account The {@link Account} to test * @param features An array of the account features to check @@ -627,11 +629,9 @@ public class AccountManager { * @param handler {@link Handler} identifying the callback thread, * null for the main thread * @return An {@link AccountManagerFuture} which resolves to a Boolean, - * true if the account exists and has all of the specified features. - * @throws SecurityException if the specified account is managed by an - * application whose signature doesn't match the caller's signature. + * true if the account exists and has all of the specified features. */ - @NonNull + @RequiresPermission(GET_ACCOUNTS) public AccountManagerFuture<Boolean> hasFeatures(final Account account, final String[] features, AccountManagerCallback<Boolean> callback, Handler handler) { @@ -654,10 +654,9 @@ public class AccountManager { /** * Lists all accounts of a type which have certain features. The account - * type identifies the authenticator (see {@link #getAccountsByType}). Said - * authenticator must be in a package whose signature matches the callers - * package signature. Account features are authenticator-specific string tokens - * identifying boolean account properties (see {@link #hasFeatures}). + * type identifies the authenticator (see {@link #getAccountsByType}). + * Account features are authenticator-specific string tokens identifying + * boolean account properties (see {@link #hasFeatures}). * * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator, * which may contact the server or do other work to check account features, @@ -666,14 +665,19 @@ public class AccountManager { * <p>This method may be called from any thread, but the returned * {@link AccountManagerFuture} must not be used on the main thread. * - * <p><b>NOTE:</b> Apps targeting API level 23 or earlier will continue to - * behave as they did on devices that support API level 23. In particular - * they may still require the GET_ACCOUNTS permission. See docs for this - * function in API level 23. + * <p>Clients of this method that have not been granted the + * {@link android.Manifest.permission#GET_ACCOUNTS} permission, + * will only see those accounts managed by AbstractAccountAuthenticators whose + * signature matches the client. * * @param type The type of accounts to return, must not be null * @param features An array of the account features to require, * may be null or empty + * + * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before, + * GET_ACCOUNTS permission is needed for those platforms, irrespective of uid + * or signature match. See docs for this function in API level 22. + * * @param callback Callback to invoke when the request completes, * null for no callback * @param handler {@link Handler} identifying the callback thread, @@ -682,7 +686,7 @@ public class AccountManager { * {@link Account}, one per account of the specified type which * matches the requested features. */ - @NonNull + @RequiresPermission(GET_ACCOUNTS) public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( final String type, final String[] features, AccountManagerCallback<Account[]> callback, Handler handler) { diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 1ab55dd7c1d9..980329fe31c8 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -1030,6 +1030,20 @@ public final class AnimatorSet extends Animator { } } + /** + * @hide + * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order + * if defined (i.e. sequential or together), then we can use the flag instead of calculate + * dynamically. + * @return whether all the animators in the set are supposed to play together + */ + public boolean shouldPlayTogether() { + updateAnimatorsDuration(); + createDependencyGraph(); + // All the child nodes are set out to play right after the delay animation + return mRootNode.mChildNodes.size() == mNodes.size() - 1; + } + @Override public long getTotalDuration() { updateAnimatorsDuration(); diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java index 2a47b68a41b0..8230ac5bbfc5 100644 --- a/core/java/android/animation/PathKeyframes.java +++ b/core/java/android/animation/PathKeyframes.java @@ -231,7 +231,7 @@ class PathKeyframes implements Keyframes { } } - private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { + abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes { @Override public Class getType() { return Integer.class; @@ -243,7 +243,7 @@ class PathKeyframes implements Keyframes { } } - private abstract static class FloatKeyframesBase extends SimpleKeyframes + abstract static class FloatKeyframesBase extends SimpleKeyframes implements FloatKeyframes { @Override public Class getType() { diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java index e993cca9e325..6ba5b968dfe7 100644 --- a/core/java/android/animation/PropertyValuesHolder.java +++ b/core/java/android/animation/PropertyValuesHolder.java @@ -21,6 +21,7 @@ import android.graphics.PointF; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Log; +import android.util.PathParser; import android.util.Property; import java.lang.reflect.InvocationTargetException; @@ -1046,6 +1047,43 @@ public class PropertyValuesHolder implements Cloneable { return mAnimatedValue; } + /** + * PropertyValuesHolder is Animators use to hold internal animation related data. + * Therefore, in order to replicate the animation behavior, we need to get data out of + * PropertyValuesHolder. + * @hide + */ + public void getPropertyValues(PropertyValues values) { + init(); + values.propertyName = mPropertyName; + values.type = mValueType; + values.startValue = mKeyframes.getValue(0); + if (values.startValue instanceof PathParser.PathData) { + // PathData evaluator returns the same mutable PathData object when query fraction, + // so we have to make a copy here. + values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue); + } + values.endValue = mKeyframes.getValue(1); + if (values.endValue instanceof PathParser.PathData) { + // PathData evaluator returns the same mutable PathData object when query fraction, + // so we have to make a copy here. + values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue); + } + // TODO: We need a better way to get data out of keyframes. + if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase + || mKeyframes instanceof PathKeyframes.IntKeyframesBase) { + // property values will animate based on external data source (e.g. Path) + values.dataSource = new PropertyValues.DataSource() { + @Override + public Object getValueAtFraction(float fraction) { + return mKeyframes.getValue(fraction); + } + }; + } else { + values.dataSource = null; + } + } + @Override public String toString() { return mPropertyName + ": " + mKeyframes.toString(); @@ -1601,6 +1639,24 @@ public class PropertyValuesHolder implements Cloneable { } }; + /** + * @hide + */ + public static class PropertyValues { + public String propertyName; + public Class type; + public Object startValue; + public Object endValue; + public DataSource dataSource = null; + public interface DataSource { + Object getValueAtFraction(float fraction); + } + public String toString() { + return ("property name: " + propertyName + ", type: " + type + ", startValue: " + + startValue.toString() + ", endValue: " + endValue.toString()); + } + } + native static private long nGetIntMethod(Class targetClass, String methodName); native static private long nGetFloatMethod(Class targetClass, String methodName); native static private long nGetMultipleIntMethod(Class targetClass, String methodName, diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 378e44880023..acd4ab8286fc 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1837,6 +1837,7 @@ public class Activity extends ContextThemeWrapper public void onMultiWindowChanged(boolean inMultiWindow) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onMultiWindowChanged " + this + ": " + inMultiWindow); + mFragments.dispatchMultiWindowChanged(inMultiWindow); if (mWindow != null) { mWindow.onMultiWindowChanged(); } @@ -1862,9 +1863,11 @@ public class Activity extends ContextThemeWrapper * * @param inPictureInPicture True if the activity is in picture-in-picture mode. */ + @CallSuper public void onPictureInPictureChanged(boolean inPictureInPicture) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPictureInPictureChanged " + this + ": " + inPictureInPicture); + mFragments.dispatchPictureInPictureChanged(inPictureInPicture); } /** diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b5b27536d4c6..c22a94187fe0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5078,11 +5078,11 @@ public final class ActivityThread { Log.e(TAG, "Unable to setupGraphicsSupport and setupJitProfileSupport " + "due to missing code-cache directory"); } - } - // Add the lib dir path to hardware renderer so that vulkan layers - // can be searched for within that directory. - ThreadedRenderer.setLibDir(data.info.getLibDir()); + // Add the lib dir path to hardware renderer so that vulkan layers + // can be searched for within that directory. + ThreadedRenderer.setLibDir(data.info.getLibDir()); + } // Install the Network Security Config Provider. This must happen before the application // code is loaded to prevent issues with instances of TLS objects being created before diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 220fb607289a..c071162ca5b9 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -326,8 +326,7 @@ public class ApplicationPackageManager extends PackageManager { // This is a temporary hack. Callers must use // createPackageContext(packageName).getApplicationInfo() to // get the right paths. - maybeAdjustApplicationInfo(ai); - return ai; + return maybeAdjustApplicationInfo(ai); } } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); @@ -336,7 +335,7 @@ public class ApplicationPackageManager extends PackageManager { throw new NameNotFoundException(packageName); } - private static void maybeAdjustApplicationInfo(ApplicationInfo info) { + private static ApplicationInfo maybeAdjustApplicationInfo(ApplicationInfo info) { // If we're dealing with a multi-arch application that has both // 32 and 64 bit shared libraries, we might need to choose the secondary // depending on what the current runtime's instruction set is. @@ -353,9 +352,12 @@ public class ApplicationPackageManager extends PackageManager { // Everything will be set up correctly because info.nativeLibraryDir will // correspond to the right ISA. if (runtimeIsa.equals(secondaryIsa)) { - info.nativeLibraryDir = info.secondaryNativeLibraryDir; + ApplicationInfo modified = new ApplicationInfo(info); + modified.nativeLibraryDir = info.secondaryNativeLibraryDir; + return modified; } } + return info; } @Override diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 20eaf0b8dca4..688c9f8e4fc4 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1576,6 +1576,25 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene public void onSaveInstanceState(Bundle outState) { } + /** + * Called when the Fragment's activity changes from fullscreen mode to multi-window mode and + * visa-versa. This is generally tied to {@link Activity#onMultiWindowChanged} of the containing + * Activity. + * + * @param inMultiWindow True if the activity is in multi-window mode. + */ + public void onMultiWindowChanged(boolean inMultiWindow) { + } + + /** + * Called by the system when the activity changes to and from picture-in-picture mode. This is + * generally tied to {@link Activity#onPictureInPictureChanged} of the containing Activity. + * + * @param inPictureInPicture True if the activity is in picture-in-picture mode. + */ + public void onPictureInPictureChanged(boolean inPictureInPicture) { + } + public void onConfigurationChanged(Configuration newConfig) { mCalled = true; } @@ -2308,6 +2327,20 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } } + void performMultiWindowChanged(boolean inMultiWindow) { + onMultiWindowChanged(inMultiWindow); + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchMultiWindowChanged(inMultiWindow); + } + } + + void performPictureInPictureChanged(boolean inPictureInPicture) { + onPictureInPictureChanged(inPictureInPicture); + if (mChildFragmentManager != null) { + mChildFragmentManager.dispatchPictureInPictureChanged(inPictureInPicture); + } + } + void performConfigurationChanged(Configuration newConfig) { onConfigurationChanged(newConfig); if (mChildFragmentManager != null) { diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java index 28dadfa78b32..a9270bc43e09 100644 --- a/core/java/android/app/FragmentController.java +++ b/core/java/android/app/FragmentController.java @@ -219,6 +219,28 @@ public class FragmentController { } /** + * Lets all Fragments managed by the controller's FragmentManager know the multi-window mode of + * the activity changed. + * <p>Call when the multi-window mode of the activity changed. + * + * @see Fragment#onMultiWindowChanged + */ + public void dispatchMultiWindowChanged(boolean inMultiWindow) { + mHost.mFragmentManager.dispatchMultiWindowChanged(inMultiWindow); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager know the picture-in-picture + * mode of the activity changed. + * <p>Call when the picture-in-picture mode of the activity changed. + * + * @see Fragment#onPictureInPictureChanged + */ + public void dispatchPictureInPictureChanged(boolean inPictureInPicture) { + mHost.mFragmentManager.dispatchPictureInPictureChanged(inPictureInPicture); + } + + /** * Lets all Fragments managed by the controller's FragmentManager * know a configuration change occurred. * <p>Call when there is a configuration change. diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 84ae09d7e988..32fec842ca75 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1987,7 +1987,31 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mContainer = null; mParent = null; } - + + public void dispatchMultiWindowChanged(boolean inMultiWindow) { + if (mAdded == null) { + return; + } + for (int i = mAdded.size() - 1; i >= 0; --i) { + final Fragment f = mAdded.get(i); + if (f != null) { + f.performMultiWindowChanged(inMultiWindow); + } + } + } + + public void dispatchPictureInPictureChanged(boolean inPictureInPicture) { + if (mAdded == null) { + return; + } + for (int i = mAdded.size() - 1; i >= 0; --i) { + final Fragment f = mAdded.get(i); + if (f != null) { + f.performPictureInPictureChanged(inPictureInPicture); + } + } + } + public void dispatchConfigurationChanged(Configuration newConfig) { if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 3288cd91856d..40e58af57204 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -55,6 +55,7 @@ interface INotificationManager void setImportance(String pkg, int uid, in Notification.Topic topic, int importance); int getImportance(String pkg, int uid, in Notification.Topic topic); boolean doesAppUseTopics(String pkg, int uid); + boolean hasBannedTopics(String pkg, int uid); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7f037f22aa8d..34c90c165490 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2623,9 +2623,6 @@ public class Notification implements Parcelable if (onMs != 0 || offMs != 0) { mN.flags |= FLAG_SHOW_LIGHTS; } - if ((mN.defaults & DEFAULT_LIGHTS) != 0) { - mN.flags |= FLAG_SHOW_LIGHTS; - } return this; } @@ -3603,6 +3600,10 @@ public class Notification implements Parcelable mStyle.buildStyled(mN); } + if ((mN.defaults & DEFAULT_LIGHTS) != 0) { + mN.flags |= FLAG_SHOW_LIGHTS; + } + return mN; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f04e76e22074..9bad2f9dc238 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3474,8 +3474,7 @@ public class DevicePolicyManager { public boolean isProfileOwnerApp(String packageName) { if (mService != null) { try { - ComponentName profileOwner = mService.getProfileOwner( - Process.myUserHandle().getIdentifier()); + ComponentName profileOwner = mService.getProfileOwner(myUserId()); return profileOwner != null && profileOwner.getPackageName().equals(packageName); } catch (RemoteException re) { diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl new file mode 100644 index 000000000000..8f38d6f90a7d --- /dev/null +++ b/core/java/android/content/pm/IOtaDexopt.aidl @@ -0,0 +1,49 @@ +/* +** +** Copyright 2016, 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 android.content.pm; + +/** + * A/B OTA dexopting service. + * + * {@hide} + */ +interface IOtaDexopt { + /** + * Prepare for A/B OTA dexopt. Initialize internal structures. + * + * Calls to the other methods are only valid after a call to prepare. You may not call + * prepare twice without a cleanup call. + */ + void prepare(); + + /** + * Clean up all internal state. + */ + void cleanup(); + + /** + * Check whether all updates have been performed. + */ + boolean isDone(); + + /** + * Optimize the next package. Note: this command is synchronous, that is, only returns after + * the package has been dexopted (or dexopting failed). + */ + void dexoptNextPackage(); +} diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 09b85c93e050..3802db85c1c0 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -291,6 +291,7 @@ public class PackageParser { public final boolean coreApp; public final boolean multiArch; + public final String abiOverride; public final boolean extractNativeLibs; public PackageLite(String codePath, ApkLite baseApk, String[] splitNames, @@ -307,6 +308,7 @@ public class PackageParser { this.splitRevisionCodes = splitRevisionCodes; this.coreApp = baseApk.coreApp; this.multiArch = baseApk.multiArch; + this.abiOverride = baseApk.abiOverride; this.extractNativeLibs = baseApk.extractNativeLibs; } @@ -334,11 +336,12 @@ public class PackageParser { public final Signature[] signatures; public final boolean coreApp; public final boolean multiArch; + public final String abiOverride; public final boolean extractNativeLibs; public ApkLite(String codePath, String packageName, String splitName, int versionCode, int revisionCode, int installLocation, List<VerifierInfo> verifiers, - Signature[] signatures, boolean coreApp, boolean multiArch, + Signature[] signatures, boolean coreApp, boolean multiArch, String abiOverride, boolean extractNativeLibs) { this.codePath = codePath; this.packageName = packageName; @@ -350,6 +353,7 @@ public class PackageParser { this.signatures = signatures; this.coreApp = coreApp; this.multiArch = multiArch; + this.abiOverride = abiOverride; this.extractNativeLibs = extractNativeLibs; } } @@ -793,6 +797,7 @@ public class PackageParser { } pkg.codePath = packageDir.getAbsolutePath(); + pkg.cpuAbiOverride = lite.abiOverride; return pkg; } finally { IoUtils.closeQuietly(assets); @@ -811,8 +816,8 @@ public class PackageParser { */ @Deprecated public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException { + final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (mOnlyCoreApps) { - final PackageLite lite = parseMonolithicPackageLite(apkFile, flags); if (!lite.coreApp) { throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "Not a coreApp: " + apkFile); @@ -823,6 +828,7 @@ public class PackageParser { try { final Package pkg = parseBaseApk(apkFile, assets, flags); pkg.codePath = apkFile.getAbsolutePath(); + pkg.cpuAbiOverride = lite.abiOverride; return pkg; } finally { IoUtils.closeQuietly(assets); @@ -1316,6 +1322,7 @@ public class PackageParser { int revisionCode = 0; boolean coreApp = false; boolean multiArch = false; + String abiOverride = null; boolean extractNativeLibs = true; for (int i = 0; i < attrs.getAttributeCount(); i++) { @@ -1356,6 +1363,9 @@ public class PackageParser { if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); } + if ("abiOverride".equals(attr)) { + abiOverride = attrs.getAttributeValue(i); + } if ("extractNativeLibs".equals(attr)) { extractNativeLibs = attrs.getAttributeBooleanValue(i, true); } @@ -1365,7 +1375,7 @@ public class PackageParser { return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode, revisionCode, installLocation, verifiers, signatures, coreApp, multiArch, - extractNativeLibs); + abiOverride, extractNativeLibs); } /** @@ -3237,18 +3247,20 @@ public class PackageParser { SCREEN_ORIENTATION_UNSPECIFIED); a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; - if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) { - final boolean appDefault = (owner.applicationInfo.privateFlags - & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES) != 0; - if (sa.getBoolean( - R.styleable.AndroidManifestActivity_resizeableActivity, appDefault)) { - if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, - false)) { - a.info.resizeMode = RESIZE_MODE_RESIZEABLE_AND_PIPABLE; - } else { - a.info.resizeMode = RESIZE_MODE_RESIZEABLE; - } + final boolean appDefault = (owner.applicationInfo.privateFlags + & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES) != 0; + final boolean resizeable = sa.getBoolean( + R.styleable.AndroidManifestActivity_resizeableActivity, appDefault); + + if (resizeable) { + if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture, + false)) { + a.info.resizeMode = RESIZE_MODE_RESIZEABLE_AND_PIPABLE; + } else { + a.info.resizeMode = RESIZE_MODE_RESIZEABLE; } + } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) { + a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; } else if (a.info.screenOrientation == SCREEN_ORIENTATION_UNSPECIFIED && (a.info.flags & FLAG_IMMERSIVE) == 0) { a.info.resizeMode = RESIZE_MODE_CROP_WINDOWS; diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index 8bdd42a30c78..bb0a04279904 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -175,7 +175,8 @@ public class LegacyMetadataMapper { * colorCorrection.* */ m.set(COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES, - new int[] { COLOR_CORRECTION_ABERRATION_MODE_FAST }); + new int[] { COLOR_CORRECTION_ABERRATION_MODE_FAST, + COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY }); /* * control.ae* */ @@ -210,7 +211,8 @@ public class LegacyMetadataMapper { * noiseReduction.* */ m.set(NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES, - new int[] { NOISE_REDUCTION_MODE_FAST }); + new int[] { NOISE_REDUCTION_MODE_FAST, + NOISE_REDUCTION_MODE_HIGH_QUALITY}); /* * scaler.* @@ -1389,7 +1391,22 @@ public class LegacyMetadataMapper { /* * noiseReduction.* */ - m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST); + if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) { + m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_HIGH_QUALITY); + } else { + m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST); + } + + /* + * colorCorrection.* + */ + if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) { + m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE, + COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY); + } else { + m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE, + COLOR_CORRECTION_ABERRATION_MODE_FAST); + } /* * lens.* diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java index 6a44ac50de0a..2e06d5fb3b6f 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java @@ -89,7 +89,8 @@ public class LegacyRequestMapper { COLOR_CORRECTION_ABERRATION_MODE, /*defaultValue*/COLOR_CORRECTION_ABERRATION_MODE_FAST); - if (aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_FAST) { + if (aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_FAST && + aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY) { Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " + "colorCorrection.aberrationMode = " + aberrationMode); } @@ -446,7 +447,8 @@ public class LegacyRequestMapper { NOISE_REDUCTION_MODE, /*defaultValue*/NOISE_REDUCTION_MODE_FAST); - if (mode != NOISE_REDUCTION_MODE_FAST) { + if (mode != NOISE_REDUCTION_MODE_FAST && + mode != NOISE_REDUCTION_MODE_HIGH_QUALITY) { Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " + "noiseReduction.mode = " + mode); } diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java index ce850052b19d..dc5823d80f21 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java @@ -67,7 +67,9 @@ public class LegacyResultMapper { /* * Attempt to look up the result from the cache if the parameters haven't changed */ - if (mCachedRequest != null && legacyRequest.parameters.same(mCachedRequest.parameters)) { + if (mCachedRequest != null && + legacyRequest.parameters.same(mCachedRequest.parameters) && + legacyRequest.captureRequest.equals(mCachedRequest.captureRequest)) { result = new CameraMetadataNative(mCachedResult); cached = true; } else { @@ -124,8 +126,8 @@ public class LegacyResultMapper { */ // colorCorrection.aberrationMode { - // Always hardcoded to FAST - result.set(COLOR_CORRECTION_ABERRATION_MODE, COLOR_CORRECTION_ABERRATION_MODE_FAST); + result.set(COLOR_CORRECTION_ABERRATION_MODE, + request.get(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE)); } /* @@ -282,7 +284,7 @@ public class LegacyResultMapper { * noiseReduction.* */ // noiseReduction.mode - result.set(NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST); + result.set(NOISE_REDUCTION_MODE, request.get(CaptureRequest.NOISE_REDUCTION_MODE)); return result; } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 25b38c07aa66..7004e9725bd2 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3215,11 +3215,24 @@ public class ConnectivityManager { /** * Device is restricting metered network activity while application is running on background. + * <p> * In this state, application should not try to use the network while running on background, * because it would be denied. */ public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; + /** + * A change in the background metered network activity restriction has occurred. + * <p> + * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction + * applies to them. + * <p> + * This is only sent to registered receivers, not manifest receivers. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = + "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = false, value = { diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 1e4ee4bc2ef5..b2cabd860bfa 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -373,6 +373,12 @@ public class Process { **/ public static final int THREAD_GROUP_AUDIO_SYS = 4; + /** + * Thread group for top foreground app. + * @hide + **/ + public static final int THREAD_GROUP_TOP_APP = 5; + public static final int SIGNAL_QUIT = 3; public static final int SIGNAL_KILL = 9; public static final int SIGNAL_USR1 = 10; diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 407ff08ef2de..154c9bbab312 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -67,6 +67,7 @@ public class RecoverySystem { /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */ private static File RECOVERY_DIR = new File("/cache/recovery"); + private static File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map"); private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); private static File UNCRYPT_FILE = new File(RECOVERY_DIR, "uncrypt_file"); private static File LOG_FILE = new File(RECOVERY_DIR, "log"); @@ -473,7 +474,9 @@ public class RecoverySystem { Log.e(TAG, "Error reading recovery log", e); } - if (UNCRYPT_FILE.exists()) { + // Only remove the OTA package if it's partially processed (uncrypt'd). + boolean reservePackage = BLOCK_MAP_FILE.exists(); + if (!reservePackage && UNCRYPT_FILE.exists()) { String filename = null; try { filename = FileUtils.readTextFile(UNCRYPT_FILE, 0, null); @@ -492,11 +495,18 @@ public class RecoverySystem { } } - // Delete everything in RECOVERY_DIR except those beginning - // with LAST_PREFIX + // We keep the update logs (beginning with LAST_PREFIX), and optionally + // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE + // will be created at the end of a successful uncrypt. If seeing this + // file, we keep the block map file and the file that contains the + // package name (UNCRYPT_FILE). This is to reduce the work for GmsCore + // to avoid re-downloading everything again. String[] names = RECOVERY_DIR.list(); for (int i = 0; names != null && i < names.length; i++) { if (names[i].startsWith(LAST_PREFIX)) continue; + if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue; + if (reservePackage && names[i].equals(UNCRYPT_FILE.getName())) continue; + recursiveDelete(new File(RECOVERY_DIR, names[i])); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index ad7b4e21d368..dc0e249d72ec 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -64,8 +64,8 @@ public class UserManager { * use {@link android.accounts.AccountManager} APIs to add or remove accounts when account * management is disallowed. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -75,10 +75,10 @@ public class UserManager { /** * Specifies if a user is disallowed from changing Wi-Fi * access points. The default value is <code>false</code>. - * <p/>This restriction has no effect in a managed profile. + * <p>This restriction has no effect in a managed profile. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -89,8 +89,8 @@ public class UserManager { * Specifies if a user is disallowed from installing applications. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -101,8 +101,8 @@ public class UserManager { * Specifies if a user is disallowed from uninstalling applications. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -112,11 +112,11 @@ public class UserManager { /** * Specifies if a user is disallowed from turning on location sharing. * The default value is <code>false</code>. - * <p/>In a managed profile, location sharing always reflects the primary user's setting, but + * <p>In a managed profile, location sharing always reflects the primary user's setting, but * can be overridden and forced off by setting this restriction to true in the managed profile. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -128,8 +128,8 @@ public class UserManager { * "Unknown Sources" setting, that allows installation of apps from unknown sources. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -140,10 +140,10 @@ public class UserManager { * Specifies if a user is disallowed from configuring bluetooth. * This does <em>not</em> restrict the user from turning bluetooth on or off. * The default value is <code>false</code>. - * <p/>This restriction has no effect in a managed profile. + * <p>This restriction has no effect in a managed profile. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -155,8 +155,8 @@ public class UserManager { * USB. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -167,8 +167,8 @@ public class UserManager { * Specifies if a user is disallowed from configuring user * credentials. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -181,8 +181,8 @@ public class UserManager { * This restriction has no effect on managed profiles. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -193,8 +193,8 @@ public class UserManager { * Specifies if a user is disallowed from enabling or * accessing debugging features. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -207,8 +207,8 @@ public class UserManager { * This restriction has an effect in a managed profile only from * {@link android.os.Build.VERSION_CODES#M} * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -220,8 +220,8 @@ public class UserManager { * & portable hotspots. This can only be set by device owners and profile owners on the * primary user. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -232,11 +232,11 @@ public class UserManager { * Specifies if a user is disallowed from resetting network settings * from Settings. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. - * <p/>This restriction has no effect on secondary users and managed profiles since only the + * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can reset the network settings of the device. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -247,11 +247,11 @@ public class UserManager { * Specifies if a user is disallowed from factory resetting * from Settings. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. - * <p/>This restriction has no effect on secondary users and managed profiles since only the + * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can factory reset the device. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -262,11 +262,11 @@ public class UserManager { * Specifies if a user is disallowed from adding new users and * profiles. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. - * <p/>This restriction has no effect on secondary users and managed profiles since only the + * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can add other users. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -277,8 +277,8 @@ public class UserManager { * Specifies if a user is disallowed from disabling application * verification. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -289,11 +289,11 @@ public class UserManager { * Specifies if a user is disallowed from configuring cell * broadcasts. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. - * <p/>This restriction has no effect on secondary users and managed profiles since only the + * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can configure cell broadcasts. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -304,11 +304,11 @@ public class UserManager { * Specifies if a user is disallowed from configuring mobile * networks. This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. - * <p/>This restriction has no effect on secondary users and managed profiles since only the + * <p>This restriction has no effect on secondary users and managed profiles since only the * primary user can configure mobile networks. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -328,8 +328,8 @@ public class UserManager { * <p> * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -341,8 +341,8 @@ public class UserManager { * physical external media. This can only be set by device owners and profile owners on the * primary user. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -354,8 +354,8 @@ public class UserManager { * volume. If set, the microphone will be muted. This can only be set by device owners * and profile owners on the primary user. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -367,8 +367,8 @@ public class UserManager { * volume. If set, the master volume will be muted. This can only be set by device owners * and profile owners on the primary user. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -379,11 +379,11 @@ public class UserManager { * Specifies that the user is not allowed to make outgoing * phone calls. Emergency calls are still permitted. * The default value is <code>false</code>. - * <p/>This restriction has no effect on managed profiles since call intents are normally + * <p>This restriction has no effect on managed profiles since call intents are normally * forwarded to the primary user. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -394,8 +394,8 @@ public class UserManager { * Specifies that the user is not allowed to send or receive * SMS messages. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -407,8 +407,8 @@ public class UserManager { * device owner may wish to prevent the user from experiencing amusement or * joy while using the device. The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -428,8 +428,8 @@ public class UserManager { * <p>This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -442,8 +442,8 @@ public class UserManager { * pasted in this profile. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -454,8 +454,8 @@ public class UserManager { * Specifies if the user is not allowed to use NFC to beam out data from apps. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -492,8 +492,8 @@ public class UserManager { * This can only be set by device owners and profile owners on the primary user. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -568,8 +568,8 @@ public class UserManager { * define a host can handle intents from the managed profile. * The default value is <code>false</code>. * - * <p/>Key for user restrictions. - * <p/>Type: Boolean + * <p>Key for user restrictions. + * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -588,8 +588,8 @@ public class UserManager { * management application that sets this flag to update it when the final * restrictions are enforced. * - * <p/>Key for application restrictions. - * <p/>Type: Boolean + * <p>Key for application restrictions. + * <p>Type: Boolean * @see android.app.admin.DevicePolicyManager#setApplicationRestrictions( * android.content.ComponentName, String, Bundle) * @see android.app.admin.DevicePolicyManager#getApplicationRestrictions( diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 5997515e58d2..4b706496db20 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -438,8 +438,11 @@ public class VolumeInfo implements Parcelable { final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(uri); + + // note that docsui treats this as *force* show advanced. So sending + // false permits advanced to be shown based on user preferences. + intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary()); intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true); - intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true); return intent; } diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index 8892e3430fe4..e9c196d0cdad 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -27,6 +27,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.R; @@ -872,6 +873,23 @@ public final class PrintAttributes implements Parcelable { mPackageName = null; } + /** + * Get the Id of all predefined media sizes beside the {@link #UNKNOWN_PORTRAIT} and + * {@link #UNKNOWN_LANDSCAPE}. + * + * @return List of all predefined media sizes + * + * @hide + */ + public static @NonNull ArraySet<MediaSize> getAllPredefinedSizes() { + ArraySet<MediaSize> definedMediaSizes = new ArraySet<>(sIdToMediaSizeMap.values()); + + definedMediaSizes.remove(UNKNOWN_PORTRAIT); + definedMediaSizes.remove(UNKNOWN_LANDSCAPE); + + return definedMediaSizes; + } + /** @hide */ public MediaSize(String id, String label, String packageName, int widthMils, int heightMils, int labelResId) { diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java index 2a9d4ae762e9..7a9d0625c199 100644 --- a/core/java/android/provider/BlockedNumberContract.java +++ b/core/java/android/provider/BlockedNumberContract.java @@ -91,14 +91,6 @@ public class BlockedNumberContract { /** * Phone number to block. The system generates it from {@link #COLUMN_ORIGINAL_NUMBER} * by removing all formatting characters. - * <p>Must NOT be specified in {@code insert}. - * <p>TYPE: String</p> - */ - public static final String COLUMN_STRIPPED_NUMBER = "stripped_number"; - - /** - * Phone number to block. The system generates it from {@link #COLUMN_ORIGINAL_NUMBER} - * by removing all formatting characters. * <p>Optional in {@code insert}. When not specified, the system tries to generate it * assuming the current country. (Which will still be null if the number is not valid.) * <p>TYPE: String</p> @@ -112,17 +104,32 @@ public class BlockedNumberContract { /** @hide */ public static final String RES_NUMBER_IS_BLOCKED = "blocked"; + /** @hide */ + public static final String METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS = + "can_current_user_block_numbers"; + + /** @hide */ + public static final String RES_CAN_BLOCK_NUMBERS = "can_block"; + /** * Returns whether a given number is in the blocked list. - * - * TODO This should probably catch IllegalArgumentException to guard against the case where - * the provider is encrypted or the user is not running. - * (See addEntryAndRemoveExpiredEntries() in - * http://ag/#/c/844426/3/core/java/android/provider/CallLog.java) + * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user + * context {@code context}, this method will throw an {@link UnsupportedOperationException}. */ public static boolean isBlocked(Context context, String phoneNumber) { final Bundle res = context.getContentResolver().call(AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null); return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false); } + + /** + * Returns {@code true} if blocking numbers is supported for the current user. + * <p> Typically, blocking numbers is only supported for the primary user. + */ + public static boolean canCurrentUserBlockNumbers(Context context) { + final Bundle res = context.getContentResolver().call( + AUTHORITY_URI, METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS, null, null); + return res != null && res.getBoolean(RES_CAN_BLOCK_NUMBERS, false); + } + } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1561d46447a9..5535eaa0726d 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6895,6 +6895,15 @@ public final class Settings { public static final String TETHER_DUN_APN = "tether_dun_apn"; /** + * List of carrier apps which are whitelisted to prompt the user for install when + * a sim card with matching uicc carrier privilege rules is inserted. + * + * The value is "package1;package2;..." + * @hide + */ + public static final String CARRIER_APP_WHITELIST = "carrier_app_whitelist"; + + /** * USB Mass Storage Enabled */ public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled"; diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index 78d5bcd90624..29a72fdf2288 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -104,6 +104,7 @@ public class PathParser { } super.finalize(); } + } /** diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 34713ad487fe..501c6e9e25e5 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -33,7 +33,7 @@ import java.util.ArrayList; * @hide */ @RemoteViews.RemoteView -public class NotificationHeaderView extends LinearLayout { +public class NotificationHeaderView extends ViewGroup { public static final int NO_COLOR = -1; private final int mHeaderMinWidth; private final int mExpandTopPadding; @@ -134,27 +134,46 @@ public class NotificationHeaderView extends LinearLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - if (mProfileBadge.getVisibility() != View.GONE) { - int paddingEnd = getPaddingEnd(); - if (mShowWorkBadgeAtEnd) { - paddingEnd = mContentEndMargin; + int left = getPaddingStart(); + int childCount = getChildCount(); + int ownHeight = getHeight() - getPaddingTop() - getPaddingBottom(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + int childHeight = child.getMeasuredHeight(); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + left += params.getMarginStart(); + int right = left + child.getMeasuredWidth(); + int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f); + int bottom = top + childHeight; + int layoutLeft = left; + int layoutRight = right; + if (child == mProfileBadge) { + int paddingEnd = getPaddingEnd(); + if (mShowWorkBadgeAtEnd) { + paddingEnd = mContentEndMargin; + } + layoutRight = getWidth() - paddingEnd; + layoutLeft = layoutRight - child.getMeasuredWidth(); } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { - mProfileBadge.layout(paddingEnd, - mProfileBadge.getTop(), - paddingEnd + mProfileBadge.getMeasuredWidth(), - mProfileBadge.getBottom()); - } else { - mProfileBadge.layout(getWidth() - paddingEnd - mProfileBadge.getMeasuredWidth(), - mProfileBadge.getTop(), - getWidth() - paddingEnd, - mProfileBadge.getBottom()); + int ltrLeft = layoutLeft; + layoutLeft = getWidth() - layoutRight; + layoutRight = getWidth() - ltrLeft; } + child.layout(layoutLeft, top, layoutRight, bottom); + left = right + params.getMarginEnd(); } updateTouchListener(); } + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new ViewGroup.MarginLayoutParams(getContext(), attrs); + } + private void updateTouchListener() { if (mExpandClickListener != null) { mTouchListener.bindTouchRects(); diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 88bffb5827aa..2aace0f30139 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -22,6 +22,7 @@ import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; /** * <p>A display list records a series of graphics related operations and can replay @@ -771,6 +772,14 @@ public class RenderNode { mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this); } + public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) { + if (mOwningView == null || mOwningView.mAttachInfo == null) { + throw new IllegalStateException("Cannot start this animator on a detached view!"); + } + nAddAnimator(mNativeRenderNode, animatorSet.getAnimatorNativePtr()); + mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this); + } + public void endAllAnimators() { nEndAllAnimators(mNativeRenderNode); } diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java new file mode 100644 index 000000000000..ba592d29fa3d --- /dev/null +++ b/core/java/android/view/RenderNodeAnimatorSetHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 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 android.view; + +import android.animation.TimeInterpolator; +import com.android.internal.view.animation.FallbackLUTInterpolator; +import com.android.internal.view.animation.NativeInterpolatorFactory; +import com.android.internal.view.animation.NativeInterpolatorFactoryHelper; + +/** + * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet + * that are internal or package private to android.view package. + * + * @hide + */ +public class RenderNodeAnimatorSetHelper { + + public static RenderNode getTarget(DisplayListCanvas recordingCanvas) { + return recordingCanvas.mNode; + } + + public static long createNativeInterpolator(TimeInterpolator interpolator, long + duration) { + if (interpolator == null) { + // create LinearInterpolator + return NativeInterpolatorFactoryHelper.createLinearInterpolator(); + } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) { + return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator(); + } else { + return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration); + } + } + +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index dd0887f8f8c5..4a0a0b0dbeb7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6770,6 +6770,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setVisibleToUser(isVisibleToUser()); + if ((mAttachInfo != null) && ((mAttachInfo.mAccessibilityFetchFlags + & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0)) { + info.setImportantForAccessibility(isImportantForAccessibility()); + } else { + info.setImportantForAccessibility(true); + } + info.setPackageName(mContext.getPackageName()); info.setClassName(getAccessibilityClassName()); info.setContentDescription(getContentDescription()); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index b67338655057..bdaf291a2d3c 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; import android.graphics.Rect; @@ -568,6 +569,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000; + private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -2156,6 +2159,33 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Returns whether the node originates from a view considered important for accessibility. + * + * @return {@code true} if the node originates from a view considered important for + * accessibility, {@code false} otherwise + * + * @see View#isImportantForAccessibility() + */ + public boolean isImportantForAccessibility() { + return getBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE); + } + + /** + * Sets whether the node is considered important for accessibility. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param important {@code true} if the node is considered important for accessibility, + * {@code false} otherwise + */ + public void setImportantForAccessibility(boolean important) { + setBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE, important); + } + + /** * Gets the package this node comes from. * * @return The package name. @@ -3311,9 +3341,21 @@ public class AccessibilityNodeInfo implements Parcelable { * </li> * <li><strong>Overriden standard actions</strong> - These are actions that override * standard actions to customize them. For example, an app may add a label to the - * standard click action to announce that this action clears browsing history. + * standard {@link #ACTION_CLICK} action to announce that this action clears browsing history. * </ul> * </p> + * <p> + * Actions are typically added to an {@link AccessibilityNodeInfo} by using + * {@link AccessibilityNodeInfo#addAction(AccessibilityAction)} within + * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed + * within {@link View#performAccessibilityAction(int, Bundle)}. + * </p> + * <p class="note"> + * <strong>Note:</strong> Views which support these actions should invoke + * {@link View#setImportantForAccessibility(int)} with + * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService} + * can discover the set of supported actions. + * </p> */ public static final class AccessibilityAction { diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index fd857fd10c82..aac7bc3810e9 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -234,7 +234,6 @@ public class AlertController { int contentView = selectContentView(); mWindow.setContentView(contentView); setupView(); - setupDecor(); } private int selectContentView() { @@ -431,27 +430,6 @@ public class AlertController { return mScrollView != null && mScrollView.executeKeyEvent(event); } - private void setupDecor() { - final View decor = mWindow.getDecorView(); - final View parent = mWindow.findViewById(R.id.parentPanel); - if (parent != null && decor != null) { - decor.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { - @Override - public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { - if (insets.isRound()) { - // TODO: Get the padding as a function of the window size. - int roundOffset = mContext.getResources().getDimensionPixelOffset( - R.dimen.alert_dialog_round_padding); - parent.setPadding(roundOffset, roundOffset, roundOffset, roundOffset); - } - return insets.consumeSystemWindowInsets(); - } - }); - decor.setFitsSystemWindows(true); - decor.requestApplyInsets(); - } - } - /** * Resolves whether a custom or default panel should be used. Removes the * default panel if a custom panel should be used. If the resolved panel is diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 27333915b2b6..cf13a13cafb7 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; @@ -35,6 +36,7 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -68,6 +70,7 @@ import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsProto.MetricsEvent; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -91,6 +94,11 @@ public class ChooserActivity extends ResolverActivity { private ChooserListAdapter mChooserListAdapter; private ChooserRowAdapter mChooserRowAdapter; + private SharedPreferences mPinnedSharedPrefs; + private static final float PINNED_TARGET_SCORE_BOOST = 1000.f; + private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings"; + private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; + private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); private static final int CHOOSER_TARGET_SERVICE_RESULT = 1; @@ -207,12 +215,30 @@ public class ChooserActivity extends ResolverActivity { mRefinementIntentSender = intent.getParcelableExtra( Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); setSafeForwardingMode(true); + + mPinnedSharedPrefs = getPinnedSharedPrefs(this); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); } + static SharedPreferences getPinnedSharedPrefs(Context context) { + // The code below is because in the android:ui process, no one can hear you scream. + // The package info in the context isn't initialized in the way it is for normal apps, + // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we + // build the path manually below using the same policy that appears in ContextImpl. + // This fails silently under the hood if there's a problem, so if we find ourselves in + // the case where we don't have access to credential encrypted storage we just won't + // have our pinned target info. + final File prefsFile = new File(new File( + Environment.getDataUserCredentialEncryptedPackageDirectory(null, + context.getUserId(), context.getPackageName()), + "shared_prefs"), + PINNED_SHARED_PREFS_NAME + ".xml"); + return context.getSharedPreferences(prefsFile, MODE_PRIVATE); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -243,7 +269,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - void onActivityStarted(TargetInfo cti) { + public void onActivityStarted(TargetInfo cti) { if (mChosenComponentSender != null) { final ComponentName target = cti.getResolvedComponentName(); if (target != null) { @@ -259,7 +285,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, + public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, boolean alwaysUseOption) { final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; mChooserListAdapter = (ChooserListAdapter) adapter; @@ -272,17 +298,17 @@ public class ChooserActivity extends ResolverActivity { } @Override - int getLayoutResource() { + public int getLayoutResource() { return R.layout.chooser_grid; } @Override - boolean shouldGetActivityMetadata() { + public boolean shouldGetActivityMetadata() { return true; } @Override - boolean shouldAutoLaunchSingleChoice(TargetInfo target) { + public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { final Intent intent = target.getResolvedIntent(); final ResolveInfo resolve = target.getResolveInfo(); @@ -299,6 +325,16 @@ public class ChooserActivity extends ResolverActivity { return false; } + @Override + public void showTargetDetails(ResolveInfo ri) { + ComponentName name = ri.activityInfo.getComponentName(); + boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false); + ResolverTargetActionsDialogFragment f = + new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()), + name, pinned); + f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG); + } + private void modifyTargetIntent(Intent in) { final String action = in.getAction(); if (Intent.ACTION_SEND.equals(action) || @@ -340,7 +376,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - void startSelected(int which, boolean always, boolean filtered) { + public void startSelected(int which, boolean always, boolean filtered) { super.startSelected(which, always, filtered); if (mChooserListAdapter != null) { @@ -471,7 +507,7 @@ public class ChooserActivity extends ResolverActivity { mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); } - void onSetupVoiceInteraction() { + public void onSetupVoiceInteraction() { // Do nothing. We'll send the voice stuff ourselves. } @@ -543,7 +579,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, + public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents, @@ -711,6 +747,11 @@ public class ChooserActivity extends ResolverActivity { } return results; } + + @Override + public boolean isPinned() { + return mSourceInfo != null ? mSourceInfo.isPinned() : false; + } } public class ChooserListAdapter extends ResolveListAdapter { @@ -777,6 +818,20 @@ public class ChooserActivity extends ResolverActivity { } @Override + public boolean isComponentPinned(ComponentName name) { + return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false); + } + + @Override + public float getScore(DisplayResolveInfo target) { + float score = super.getScore(target); + if (target.isPinned()) { + score += PINNED_TARGET_SCORE_BOOST; + } + return score; + } + + @Override public View onCreateView(ViewGroup parent) { return mInflater.inflate( com.android.internal.R.layout.resolve_grid_item, parent, false); @@ -1121,7 +1176,7 @@ public class ChooserActivity extends ResolverActivity { v.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { - showAppDetails( + showTargetDetails( mChooserListAdapter.resolveInfoForPosition( holder.itemIndices[column], true)); return true; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index ec148c554bb7..53e329d090cf 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -22,6 +22,7 @@ import android.app.ActivityThread; import android.app.VoiceInteractor.PickOptionRequest; import android.app.VoiceInteractor.PickOptionRequest.Option; import android.app.VoiceInteractor.Prompt; +import android.content.pm.ComponentInfo; import android.os.AsyncTask; import android.provider.Settings; import android.text.TextUtils; @@ -336,12 +337,12 @@ public class ResolverActivity extends Activity { /** * Perform any initialization needed for voice interaction. */ - void onSetupVoiceInteraction() { + public void onSetupVoiceInteraction() { // Do it right now. Subclasses may delay this and send it later. sendVoiceChoicesIfNeeded(); } - void sendVoiceChoicesIfNeeded() { + public void sendVoiceChoicesIfNeeded() { if (!isVoiceInteraction()) { // Clearly not needed. return; @@ -382,7 +383,7 @@ public class ResolverActivity extends Activity { return null; } - int getLayoutResource() { + public int getLayoutResource() { return R.layout.resolver_list; } @@ -591,7 +592,7 @@ public class ResolverActivity extends Activity { mAlwaysUseOption); } - void startSelected(int which, boolean always, boolean filtered) { + public void startSelected(int which, boolean always, boolean filtered) { if (isFinishing()) { return; } @@ -761,7 +762,7 @@ public class ResolverActivity extends Activity { return true; } - void safelyStartActivity(TargetInfo cti) { + public void safelyStartActivity(TargetInfo cti) { // If needed, show that intent is forwarded // from managed profile to owner or other way around. if (mProfileSwitchMessageId != -1) { @@ -791,26 +792,26 @@ public class ResolverActivity extends Activity { } } - void onActivityStarted(TargetInfo cti) { + public void onActivityStarted(TargetInfo cti) { // Do nothing } - boolean shouldGetActivityMetadata() { + public boolean shouldGetActivityMetadata() { return false; } - boolean shouldAutoLaunchSingleChoice(TargetInfo target) { + public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { return true; } - void showAppDetails(ResolveInfo ri) { + public void showTargetDetails(ResolveInfo ri) { Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); startActivity(in); } - ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, + public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { return new ResolveListAdapter(context, payloadIntents, initialIntents, rList, @@ -820,7 +821,7 @@ public class ResolverActivity extends Activity { /** * Returns true if the activity is finishing and creation should halt */ - boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, + public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean alwaysUseOption) { // The last argument of createAdapter is whether to do special handling // of the last used choice to highlight it in the list. We need to always @@ -867,7 +868,7 @@ public class ResolverActivity extends Activity { return false; } - void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, + public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, boolean alwaysUseOption) { final boolean useHeader = adapter.hasFilteredItem(); final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; @@ -898,7 +899,7 @@ public class ResolverActivity extends Activity { && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName); } - final class DisplayResolveInfo implements TargetInfo { + public final class DisplayResolveInfo implements TargetInfo { private final ResolveInfo mResolveInfo; private final CharSequence mDisplayLabel; private Drawable mDisplayIcon; @@ -906,8 +907,9 @@ public class ResolverActivity extends Activity { private final CharSequence mExtendedInfo; private final Intent mResolvedIntent; private final List<Intent> mSourceIntents = new ArrayList<>(); + private boolean mPinned; - DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, + public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) { mSourceIntents.add(originalIntent); mResolveInfo = pri; @@ -932,6 +934,7 @@ public class ResolverActivity extends Activity { mExtendedInfo = other.mExtendedInfo; mResolvedIntent = new Intent(other.mResolvedIntent); mResolvedIntent.fillIn(fillInIntent, flags); + mPinned = other.mPinned; } public ResolveInfo getResolveInfo() { @@ -1026,6 +1029,15 @@ public class ResolverActivity extends Activity { activity.startActivityAsUser(mResolvedIntent, options, user); return false; } + + @Override + public boolean isPinned() { + return mPinned; + } + + public void setPinned(boolean pinned) { + mPinned = pinned; + } } /** @@ -1039,7 +1051,7 @@ public class ResolverActivity extends Activity { * * @return the resolved intent for this target */ - public Intent getResolvedIntent(); + Intent getResolvedIntent(); /** * Get the resolved component name that represents this target. Note that this may not @@ -1048,7 +1060,7 @@ public class ResolverActivity extends Activity { * * @return the resolved ComponentName for this target */ - public ComponentName getResolvedComponentName(); + ComponentName getResolvedComponentName(); /** * Start the activity referenced by this target. @@ -1057,7 +1069,7 @@ public class ResolverActivity extends Activity { * @param options ActivityOptions bundle * @return true if the start completed successfully */ - public boolean start(Activity activity, Bundle options); + boolean start(Activity activity, Bundle options); /** * Start the activity referenced by this target as if the ResolverActivity's caller @@ -1068,7 +1080,7 @@ public class ResolverActivity extends Activity { * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller * @return true if the start completed successfully */ - public boolean startAsCaller(Activity activity, Bundle options, int userId); + boolean startAsCaller(Activity activity, Bundle options, int userId); /** * Start the activity referenced by this target as a given user. @@ -1078,7 +1090,7 @@ public class ResolverActivity extends Activity { * @param user handle for the user to start the activity as * @return true if the start completed successfully */ - public boolean startAsUser(Activity activity, Bundle options, UserHandle user); + boolean startAsUser(Activity activity, Bundle options, UserHandle user); /** * Return the ResolveInfo about how and why this target matched the original query @@ -1086,14 +1098,14 @@ public class ResolverActivity extends Activity { * * @return ResolveInfo representing this target's match */ - public ResolveInfo getResolveInfo(); + ResolveInfo getResolveInfo(); /** * Return the human-readable text label for this target. * * @return user-visible target label */ - public CharSequence getDisplayLabel(); + CharSequence getDisplayLabel(); /** * Return any extended info for this target. This may be used to disambiguate @@ -1101,35 +1113,40 @@ public class ResolverActivity extends Activity { * * @return human-readable disambig string or null if none present */ - public CharSequence getExtendedInfo(); + CharSequence getExtendedInfo(); /** * @return The drawable that should be used to represent this target */ - public Drawable getDisplayIcon(); + Drawable getDisplayIcon(); /** * @return The (small) icon to badge the target with */ - public Drawable getBadgeIcon(); + Drawable getBadgeIcon(); /** * @return The content description for the badge icon */ - public CharSequence getBadgeContentDescription(); + CharSequence getBadgeContentDescription(); /** * Clone this target with the given fill-in information. */ - public TargetInfo cloneFilledIn(Intent fillInIntent, int flags); + TargetInfo cloneFilledIn(Intent fillInIntent, int flags); /** * @return the list of supported source intents deduped against this single target */ - public List<Intent> getAllSourceIntents(); + List<Intent> getAllSourceIntents(); + + /** + * @return true if this target should be pinned to the front by the request of the user + */ + boolean isPinned(); } - class ResolveListAdapter extends BaseAdapter { + public class ResolveListAdapter extends BaseAdapter { private final List<Intent> mIntents; private final Intent[] mInitialIntents; private final List<ResolveInfo> mBaseResolveList; @@ -1376,9 +1393,12 @@ public class ResolverActivity extends Activity { } } if (!found) { - into.add(new ResolvedComponentInfo(new ComponentName( - newInfo.activityInfo.packageName, newInfo.activityInfo.name), - intent, newInfo)); + final ComponentName name = new ComponentName( + newInfo.activityInfo.packageName, newInfo.activityInfo.name); + final ResolvedComponentInfo rci = new ResolvedComponentInfo(name, + intent, newInfo); + rci.setPinned(isComponentPinned(name)); + into.add(rci); } } } @@ -1454,6 +1474,7 @@ public class ResolverActivity extends Activity { final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, extraInfo, replaceIntent); + dri.setPinned(rci.isPinned()); addResolveInfo(dri); if (replaceIntent == intent) { // Only add alternates if we didn't get a specific replacement from @@ -1537,11 +1558,11 @@ public class ResolverActivity extends Activity { return false; } - protected int getDisplayResolveInfoCount() { + public int getDisplayResolveInfoCount() { return mDisplayList.size(); } - protected DisplayResolveInfo getDisplayResolveInfo(int index) { + public DisplayResolveInfo getDisplayResolveInfo(int index) { // Used to query services. We only query services for primary targets, not alternates. return mDisplayList.get(index); } @@ -1571,6 +1592,10 @@ public class ResolverActivity extends Activity { return !TextUtils.isEmpty(info.getExtendedInfo()); } + public boolean isComponentPinned(ComponentName name) { + return false; + } + public final void bindView(int position, View view) { onBindView(view, getItem(position)); } @@ -1607,6 +1632,7 @@ public class ResolverActivity extends Activity { static final class ResolvedComponentInfo { public final ComponentName name; + private boolean mPinned; private final List<Intent> mIntents = new ArrayList<>(); private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); @@ -1649,6 +1675,14 @@ public class ResolverActivity extends Activity { } return -1; } + + public boolean isPinned() { + return mPinned; + } + + public void setPinned(boolean pinned) { + mPinned = pinned; + } } static class ViewHolder { @@ -1702,7 +1736,7 @@ public class ResolverActivity extends Activity { return false; } ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true); - showAppDetails(ri); + showTargetDetails(ri); return true; } diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java index 31556e29bc32..0ae21c61112f 100644 --- a/core/java/com/android/internal/app/ResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverComparator.java @@ -47,8 +47,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { private static final boolean DEBUG = false; - // Two weeks - private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; + // One week + private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7; private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12; @@ -171,15 +171,27 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> { } } - if (mStats != null) { - final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName( - lhs.activityInfo.packageName, lhs.activityInfo.name)); - final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName( - rhs.activityInfo.packageName, rhs.activityInfo.name)); - final float diff = rhsTarget.score - lhsTarget.score; + final boolean lPinned = lhsp.isPinned(); + final boolean rPinned = rhsp.isPinned(); - if (diff != 0) { - return diff > 0 ? 1 : -1; + if (lPinned && !rPinned) { + return -1; + } else if (!lPinned && rPinned) { + return 1; + } + + // Pinned items stay stable within a normal lexical sort and ignore scoring. + if (!lPinned && !rPinned) { + if (mStats != null) { + final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName( + lhs.activityInfo.packageName, lhs.activityInfo.name)); + final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName( + rhs.activityInfo.packageName, rhs.activityInfo.name)); + final float diff = rhsTarget.score - lhsTarget.score; + + if (diff != 0) { + return diff > 0 ? 1 : -1; + } } } diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java new file mode 100644 index 000000000000..8156f79f3be3 --- /dev/null +++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 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.internal.app; + +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; + +import com.android.internal.R; + +/** + * Shows a dialog with actions to take on a chooser target + */ +public class ResolverTargetActionsDialogFragment extends DialogFragment + implements DialogInterface.OnClickListener { + private static final String NAME_KEY = "componentName"; + private static final String PINNED_KEY = "pinned"; + private static final String TITLE_KEY = "title"; + + // Sync with R.array.resolver_target_actions_* resources + private static final int TOGGLE_PIN_INDEX = 0; + private static final int APP_INFO_INDEX = 1; + + public ResolverTargetActionsDialogFragment() { + } + + public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name, + boolean pinned) { + Bundle args = new Bundle(); + args.putCharSequence(TITLE_KEY, title); + args.putParcelable(NAME_KEY, name); + args.putBoolean(PINNED_KEY, pinned); + setArguments(args); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Bundle args = getArguments(); + final int itemRes = args.getBoolean(PINNED_KEY, false) + ? R.array.resolver_target_actions_unpin + : R.array.resolver_target_actions_pin; + return new Builder(getContext()) + .setCancelable(true) + .setItems(itemRes, this) + .setTitle(args.getCharSequence(TITLE_KEY)) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final Bundle args = getArguments(); + ComponentName name = args.getParcelable(NAME_KEY); + switch (which) { + case TOGGLE_PIN_INDEX: + SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext()); + final String key = name.flattenToString(); + boolean currentVal = sp.getBoolean(name.flattenToString(), false); + if (currentVal) { + sp.edit().remove(key).apply(); + } else { + sp.edit().putBoolean(key, true).apply(); + } + + // Force the chooser to requery and resort things + getActivity().recreate(); + break; + case APP_INFO_INDEX: + Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", name.getPackageName(), null)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + startActivity(in); + break; + } + dismiss(); + } +} diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index de54d96df78b..5047c4c11889 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -289,12 +289,17 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height); final Drawable drawable = mUserCaptionBackgroundDrawable != null ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable; - drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); - drawable.draw(canvas); + + if (drawable != null) { + drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight); + drawable.draw(canvas); + } // The backdrop: clear everything with the background. Clipping is done elsewhere. - mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); - mResizingBackgroundDrawable.draw(canvas); + if (mResizingBackgroundDrawable != null) { + mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height); + mResizingBackgroundDrawable.draw(canvas); + } mFrameAndBackdropNode.end(canvas); if (mSystemBarBackgroundNode != null && mStatusBarColor != null) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 36009dcf8847..1a20e5ce5793 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -205,6 +205,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private float mAvailableWidth; String mLogTag = TAG; + private final Rect mFloatingInsets = new Rect(); + private boolean mApplyFloatingVerticalInsets = false; + private boolean mApplyFloatingHorizontalInsets = false; DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) { @@ -567,6 +570,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final int heightMode = getMode(heightMeasureSpec); boolean fixedWidth = false; + mApplyFloatingHorizontalInsets = false; if (widthMode == AT_MOST) { final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor; if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { @@ -584,10 +588,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind widthMeasureSpec = MeasureSpec.makeMeasureSpec( Math.min(w, widthSize), EXACTLY); fixedWidth = true; + } else { + widthMeasureSpec = MeasureSpec.makeMeasureSpec( + widthMeasureSpec - mFloatingInsets.left - mFloatingInsets.right, + AT_MOST); + mApplyFloatingHorizontalInsets = true; } } } + mApplyFloatingVerticalInsets = false; if (heightMode == AT_MOST) { final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor : mWindow.mFixedHeightMinor; @@ -601,10 +611,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind h = 0; } if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (h > 0) { - final int heightSize = MeasureSpec.getSize(heightMeasureSpec); heightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.min(h, heightSize), EXACTLY); + } else if ((mWindow.getAttributes().flags & FLAG_FULLSCREEN) == 0) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec( + heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST); + mApplyFloatingVerticalInsets = true; } } } @@ -672,6 +686,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (mOutsets.top > 0) { offsetTopAndBottom(-mOutsets.top); } + if (mApplyFloatingVerticalInsets) { + offsetTopAndBottom(mFloatingInsets.top); + } + if (mApplyFloatingHorizontalInsets) { + offsetLeftAndRight(mFloatingInsets.left); + } // If the application changed its SystemUI metrics, we might also have to adapt // our shadow elevation. @@ -868,6 +888,25 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { + final WindowManager.LayoutParams attrs = mWindow.getAttributes(); + mFloatingInsets.setEmpty(); + if ((attrs.flags & FLAG_FULLSCREEN) == 0) { + // For dialog windows we want to make sure they don't go over the status bar or nav bar. + // We consume the system insets and we will reuse them later during the measure phase. + // We allow the app to ignore this and handle insets itself by using FLAG_FULLSCREEN. + if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) { + mFloatingInsets.top = insets.getSystemWindowInsetTop(); + mFloatingInsets.bottom = insets.getSystemWindowInsetBottom(); + insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, + insets.getSystemWindowInsetRight(), 0); + } + if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) { + mFloatingInsets.left = insets.getSystemWindowInsetTop(); + mFloatingInsets.right = insets.getSystemWindowInsetBottom(); + insets = insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), + 0, insets.getSystemWindowInsetBottom()); + } + } mFrameOffsets.set(insets.getSystemWindowInsets()); insets = updateColorViews(insets, true /* animate */); insets = updateStatusGuard(insets); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index ba456f9ce052..e239852673e8 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -883,6 +883,15 @@ public class LockPatternUtils { } /** + * Determine if the device is file encrypted + * @return true if device is file encrypted + */ + public static boolean isFileEncryptionEnabled() { + final String status = SystemProperties.get("ro.crypto.type", ""); + return "file".equalsIgnoreCase(status); + } + + /** * Clears the encryption password. */ public void clearEncryptionPassword() { diff --git a/core/jni/Android.mk b/core/jni/Android.mk index ffa3fa631b82..1b6b53ae49f4 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -51,6 +51,7 @@ LOCAL_SRC_FILES:= \ android_database_SQLiteConnection.cpp \ android_database_SQLiteGlobal.cpp \ android_database_SQLiteDebug.cpp \ + android_graphics_drawable_AnimatedVectorDrawable.cpp \ android_graphics_drawable_VectorDrawable.cpp \ android_view_DisplayEventReceiver.cpp \ android_view_DisplayListCanvas.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 2e45f8c4d9c0..223fc1af46d5 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -131,6 +131,7 @@ extern int register_android_graphics_Rasterizer(JNIEnv* env); extern int register_android_graphics_Region(JNIEnv* env); extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_Xfermode(JNIEnv* env); +extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env); extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env); extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env); @@ -1321,6 +1322,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Typeface), REG_JNI(register_android_graphics_Xfermode), REG_JNI(register_android_graphics_YuvImage), + REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), REG_JNI(register_android_graphics_drawable_VectorDrawable), REG_JNI(register_android_graphics_pdf_PdfDocument), REG_JNI(register_android_graphics_pdf_PdfEditor), diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp new file mode 100644 index 000000000000..7a3c598e0aed --- /dev/null +++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 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. + */ +#define LOG_TAG "OpenGLRenderer" + +#include "jni.h" +#include "GraphicsJNI.h" +#include "core_jni_helpers.h" +#include "log/log.h" + +#include "Animator.h" +#include "Interpolator.h" +#include "PropertyValuesAnimatorSet.h" +#include "PropertyValuesHolder.h" +#include "VectorDrawable.h" + +namespace android { +using namespace uirenderer; +using namespace VectorDrawable; + +static struct { + jclass clazz; + jmethodID callOnFinished; +} gVectorDrawableAnimatorClassInfo; + +static JNIEnv* getEnv(JavaVM* vm) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return 0; + } + return env; +} + +static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) { + class AnimationListenerBridge : public AnimationListener { + public: + AnimationListenerBridge(JNIEnv* env, jobject finishListener) { + mFinishListener = env->NewGlobalRef(finishListener); + env->GetJavaVM(&mJvm); + } + + virtual ~AnimationListenerBridge() { + if (mFinishListener) { + onAnimationFinished(NULL); + } + } + + virtual void onAnimationFinished(BaseRenderNodeAnimator*) { + LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?"); + JNIEnv* env = getEnv(mJvm); + env->CallStaticVoidMethod( + gVectorDrawableAnimatorClassInfo.clazz, + gVectorDrawableAnimatorClassInfo.callOnFinished, + mFinishListener); + releaseJavaObject(); + } + + private: + void releaseJavaObject() { + JNIEnv* env = getEnv(mJvm); + env->DeleteGlobalRef(mFinishListener); + mFinishListener = NULL; + } + + JavaVM* mJvm; + jobject mFinishListener; + }; + return new AnimationListenerBridge(env, finishListener); +} + +static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr, + jlong interpolatorPtr, jlong startDelay, jlong duration, jint repeatCount) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr); + Interpolator* interpolator = reinterpret_cast<Interpolator*>(interpolatorPtr); + set->addPropertyAnimator(holder, interpolator, startDelay, duration, repeatCount); +} + +static jlong createAnimatorSet(JNIEnv*, jobject) { + PropertyValuesAnimatorSet* animatorSet = new PropertyValuesAnimatorSet(); + return reinterpret_cast<jlong>(animatorSet); +} + +static jlong createGroupPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + jfloat startValue, jfloat endValue) { + VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(nativePtr); + GroupPropertyValuesHolder* newHolder = new GroupPropertyValuesHolder(group, propertyId, + startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathDataPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jlong startValuePtr, + jlong endValuePtr) { + VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(nativePtr); + PathData* startData = reinterpret_cast<PathData*>(startValuePtr); + PathData* endData = reinterpret_cast<PathData*>(endValuePtr); + PathDataPropertyValuesHolder* newHolder = new PathDataPropertyValuesHolder(path, + startData, endData); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathColorPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + int startValue, jint endValue) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr); + FullPathColorPropertyValuesHolder* newHolder = new FullPathColorPropertyValuesHolder(fullPath, + propertyId, startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createPathPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId, + float startValue, jfloat endValue) { + VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr); + FullPathPropertyValuesHolder* newHolder = new FullPathPropertyValuesHolder(fullPath, + propertyId, startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} + +static jlong createRootAlphaPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jfloat startValue, + float endValue) { + VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(nativePtr); + RootAlphaPropertyValuesHolder* newHolder = new RootAlphaPropertyValuesHolder(tree, + startValue, endValue); + return reinterpret_cast<jlong>(newHolder); +} +static void setPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr, + jfloatArray srcData, jint length) { + + jfloat* propertyData = env->GetFloatArrayElements(srcData, nullptr); + PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr); + holder->setPropertyDataSource(propertyData, length); + env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT); +} +static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + // TODO: keep a ref count in finish listener + AnimationListener* listener = createAnimationListener(env, finishListener); + set->start(listener); +} + +static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) { + // TODO: implement reverse +} + +static void end(JNIEnv*, jobject, jlong animatorSetPtr) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + set->end(); +} + +static void reset(JNIEnv*, jobject, jlong animatorSetPtr) { + PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr); + set->reset(); +} + +static const JNINativeMethod gMethods[] = { + {"nCreateAnimatorSet", "()J", (void*)createAnimatorSet}, + {"nAddAnimator", "(JJJJJI)V", (void*)addAnimator}, + {"nCreateGroupPropertyHolder", "!(JIFF)J", (void*)createGroupPropertyHolder}, + {"nCreatePathDataPropertyHolder", "!(JJJ)J", (void*)createPathDataPropertyHolder}, + {"nCreatePathColorPropertyHolder", "!(JIII)J", (void*)createPathColorPropertyHolder}, + {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder}, + {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder}, + {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData}, + {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start}, + {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse}, + {"nEnd", "!(J)V", (void*)end}, + {"nReset", "!(J)V", (void*)reset}, +}; + +const char* const kClassPathName = "android/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator"; +int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env) { + gVectorDrawableAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName); + gVectorDrawableAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env, + gVectorDrawableAnimatorClassInfo.clazz); + + gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie( + env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished", + "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V"); + return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp index 563ec8bd9834..e88287644555 100644 --- a/core/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp @@ -32,11 +32,6 @@ static jlong createTree(JNIEnv*, jobject, jlong groupPtr) { return reinterpret_cast<jlong>(tree); } -static void deleteTree(JNIEnv*, jobject, jlong treePtr) { - VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); - delete tree; -} - static void setTreeViewportSize(JNIEnv*, jobject, jlong treePtr, jfloat viewportWidth, jfloat viewportHeight) { VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr); @@ -333,7 +328,6 @@ static void setTrimPathOffset(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPa static const JNINativeMethod gMethods[] = { {"nCreateRenderer", "!(J)J", (void*)createTree}, - {"nDestroyRenderer", "!(J)V", (void*)deleteTree}, {"nSetRendererViewportSize", "!(JFF)V", (void*)setTreeViewportSize}, {"nSetRootAlpha", "!(JF)Z", (void*)setRootAlpha}, {"nGetRootAlpha", "!(J)F", (void*)getRootAlpha}, diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp index 048b3c71c2c3..793d1325ab6f 100644 --- a/core/jni/android_hardware_SoundTrigger.cpp +++ b/core/jni/android_hardware_SoundTrigger.cpp @@ -68,6 +68,10 @@ static struct { jfieldID data; } gSoundModelFields; +static const char* const kGenericSoundModelClassPathName = + "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel"; +static jclass gGenericSoundModelClass; + static const char* const kKeyphraseClassPathName = "android/hardware/soundtrigger/SoundTrigger$Keyphrase"; static jclass gKeyphraseClass; @@ -105,6 +109,11 @@ static const char* const kKeyphraseRecognitionEventClassPathName = static jclass gKeyphraseRecognitionEventClass; static jmethodID gKeyphraseRecognitionEventCstor; +static const char* const kGenericRecognitionEventClassPathName = + "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent"; +static jclass gGenericRecognitionEventClass; +static jmethodID gGenericRecognitionEventCstor; + static const char* const kKeyphraseRecognitionExtraClassPathName = "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra"; static jclass gKeyphraseRecognitionExtraClass; @@ -266,6 +275,12 @@ void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognitio event->capture_preamble_ms, event->trigger_in_data, jAudioFormat, jData, jExtras); env->DeleteLocalRef(jExtras); + } else if (event->type == SOUND_MODEL_TYPE_GENERIC) { + jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor, + event->status, event->model, event->capture_available, + event->capture_session, event->capture_delay_ms, + event->capture_preamble_ms, event->trigger_in_data, + jAudioFormat, jData); } else { jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor, event->status, event->model, event->capture_available, @@ -524,6 +539,9 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz, if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) { offset = sizeof(struct sound_trigger_phrase_sound_model); type = SOUND_MODEL_TYPE_KEYPHRASE; + } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) { + offset = sizeof(struct sound_trigger_generic_sound_model); + type = SOUND_MODEL_TYPE_GENERIC; } else { offset = sizeof(struct sound_trigger_sound_model); type = SOUND_MODEL_TYPE_UNKNOWN; @@ -631,6 +649,8 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz, env->DeleteLocalRef(jPhrase); } env->DeleteLocalRef(jPhrases); + } else if (type == SOUND_MODEL_TYPE_GENERIC) { + /* No initialization needed */ } status = module->loadSoundModel(memory, &handle); ALOGV("loadSoundModel status %d handle %d", status, handle); @@ -831,6 +851,9 @@ int register_android_hardware_SoundTrigger(JNIEnv *env) "Ljava/util/UUID;"); gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B"); + jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName); + gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass); + jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName); gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass); gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I"); @@ -857,6 +880,11 @@ int register_android_hardware_SoundTrigger(JNIEnv *env) gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>", "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V"); + jclass genericRecognitionEventClass = FindClassOrDie(env, + kGenericRecognitionEventClassPathName); + gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass); + gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>", + "(IIZIIIZLandroid/media/AudioFormat;[B)V"); jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName); gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a518cc464885..7729d48c2452 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -231,6 +231,7 @@ <!-- @deprecated. Only {@link android.net.ConnectivityManager.CONNECTIVITY_ACTION} is sent. --> <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE" /> <protected-broadcast android:name="android.net.conn.DATA_ACTIVITY_CHANGE" /> + <protected-broadcast android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" /> <protected-broadcast android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" /> <protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED" /> @@ -1209,8 +1210,7 @@ <eat-comment /> <!-- Allows access to the list of accounts in the Accounts Service. - <p>Protection level: dangerous - @deprecated Not operative for apps apps with targetSdkVersion >= 24. + <p>Protection level: normal --> <permission android:name="android.permission.GET_ACCOUNTS" android:permissionGroup="android.permission-group.CONTACTS" diff --git a/core/res/res/drawable-hdpi/ic_user_secure.png b/core/res/res/drawable-hdpi/ic_user_secure.png Binary files differnew file mode 100644 index 000000000000..60dcf2adc786 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_user_secure.png diff --git a/core/res/res/drawable-mdpi/ic_user_secure.png b/core/res/res/drawable-mdpi/ic_user_secure.png Binary files differnew file mode 100644 index 000000000000..0dea77a7b8b8 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_user_secure.png diff --git a/core/res/res/drawable-xhdpi/ic_user_secure.png b/core/res/res/drawable-xhdpi/ic_user_secure.png Binary files differnew file mode 100644 index 000000000000..a6ef51af2ea8 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_user_secure.png diff --git a/core/res/res/drawable-xxhdpi/ic_user_secure.png b/core/res/res/drawable-xxhdpi/ic_user_secure.png Binary files differnew file mode 100644 index 000000000000..e6154e59518d --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_user_secure.png diff --git a/core/res/res/drawable-xxxhdpi/ic_user_secure.png b/core/res/res/drawable-xxxhdpi/ic_user_secure.png Binary files differnew file mode 100644 index 000000000000..9a3959b292a3 --- /dev/null +++ b/core/res/res/drawable-xxxhdpi/ic_user_secure.png diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 163db30d6c28..b45f8bb92e18 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -22,8 +22,6 @@ android:layout_width="wrap_content" android:layout_height="48dp" android:clipChildren="false" - android:layout_gravity="start|top" - android:gravity="center_vertical" android:paddingTop="5dp" android:paddingBottom="16dp" android:paddingStart="@dimen/notification_content_margin_start" diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index a6a456415689..c083a5eb9ae9 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -402,4 +402,15 @@ <item>@color/Red_700</item> </array> + <!-- Used in ResolverTargetActionsDialogFragment --> + <string-array name="resolver_target_actions_pin"> + <item>@string/pin_target</item> + <item>@string/app_info</item> + </string-array> + + <string-array name="resolver_target_actions_unpin"> + <item>@string/unpin_target</item> + <item>@string/app_info</item> + </string-array> + </resources> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index c744bbdd7495..15a4ad853535 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -429,6 +429,12 @@ sets. --> <attr name="multiArch" format ="boolean" /> + <!-- Specify abiOverride for multiArch application. + @hide + @SystemApi + --> + <attr name="abiOverride" /> + <!-- Specify whether a component is allowed to have multiple instances of itself running in different processes. Use with the activity and provider tags. diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index bbbb963f4dff..329e2e55b0f8 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2696,6 +2696,7 @@ <public type="attr" name="endX" /> <public type="attr" name="endY" /> <public type="attr" name="offset" /> + <public type="attr" name="abiOverride" /> <public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" /> <public type="style" name="Widget.Material.SeekBar.Discrete" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 523826bf79ec..e5a6226fd937 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2869,6 +2869,14 @@ <string name="sim_added_message">Restart your device to access the cellular network.</string> <!-- See SIM_ADDED_DIALOG. This is the button of that dialog. --> <string name="sim_restart_button">Restart</string> + <!-- See Carrier_App_Dialog. This is the message of that dialog. --> + <string name="carrier_app_dialog_message">To get your new SIM working properly, you\'ll need to install and open an app from your carrier.</string> + <!-- See Carrier_App_Dialog. This is the button of that dialog. --> + <string name="carrier_app_dialog_button">GET THE APP</string> + <string name="carrier_app_dialog_not_now">NOT NOW</string> + <!-- See carrier_app_notification. This is the headline. --> + <string name="carrier_app_notification_title">New SIM inserted</string> + <string name="carrier_app_notification_text">Tap to set it up</string> <!-- Date/Time picker dialogs strings --> @@ -4206,4 +4214,15 @@ <string name="usb_mtp_launch_notification_title">Connected to <xliff:g id="product_name">%1$s</xliff:g></string> <!-- Description of notification shown after a MTP device is connected to Android. --> <string name="usb_mtp_launch_notification_description">Tap to view files</string> + + <!-- Resolver target actions strings --> + + <!-- Pin (as in to a bulletin board with a pushpin) a resolver + target to the front of the list. --> + <string name="pin_target">Pin</string> + <!-- Unpin a resolver target such that it sorts normally. --> + <string name="unpin_target">Unpin</string> + <!-- View application info for a target. --> + <string name="app_info">App info</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1f0e21ecd7fe..f75f023b8c31 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2522,7 +2522,18 @@ <java-symbol type="string" name="user_encrypted_title" /> <java-symbol type="string" name="user_encrypted_message" /> <java-symbol type="string" name="user_encrypted_detail" /> + <java-symbol type="drawable" name="ic_user_secure" /> <java-symbol type="string" name="usb_mtp_launch_notification_title" /> <java-symbol type="string" name="usb_mtp_launch_notification_description" /> + + <!-- Resolver target actions --> + <java-symbol type="array" name="resolver_target_actions_pin" /> + <java-symbol type="array" name="resolver_target_actions_unpin" /> + + <java-symbol type="string" name="carrier_app_dialog_message" /> + <java-symbol type="string" name="carrier_app_dialog_button" /> + <java-symbol type="string" name="carrier_app_dialog_not_now" /> + <java-symbol type="string" name="carrier_app_notification_title" /> + <java-symbol type="string" name="carrier_app_notification_text" /> </resources> diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd index 8c392aac2545..cfd94671604f 100644 --- a/docs/html/preview/support.jd +++ b/docs/html/preview/support.jd @@ -160,21 +160,20 @@ page.image=images/cards/card-support_16-9_2x.png still perform BTLE and WiFi scans, but only when they are in the foreground. While in the background, those apps will get no results from BTLE and WiFi scans.</li> </ul> </li> - <li>Accessing accounts + <li>Permission changes <ul> - <li>Updated the behavior of {@link android.accounts.AccountManager} account - discovery methods. + <li>Updated the user interface for permissions and enhanced some of the permissions + behaviors.</li> + <li>The {@link android.Manifest.permission#GET_ACCOUNTS} permission is now a member of the + {@link android.Manifest.permission_group#CONTACTS} permission group and it has a + {@code android:protectionLevel} of {@code dangerous}. This change means that when + targeting Android 6.0 (API level 23), you must check for and request this permission if + your app requires it. </li> - <li>The GET_ACCOUNTS permission has been deprecated. - </li> - <li>Apps targeting API level 24 should start the intent returned by - newChooseAccountIntent(...) and await the result to acquire a reference - to the user's selected account. AccountManager methods like getAccounts and - related methods will only return those accounts managed by - authenticators that match the signatures of the calling app. - </li> - <li>Apps targeting API level 23 or earlier will continue to behave as - before. + + <li>The {@code android.permission.READ_PROFILE} and {@code android.permission.WRITE_PROFILE} + permissions have been removed from the {@link android.Manifest.permission_group#CONTACTS} + permission group. </li> </ul> </li> diff --git a/docs/html/training/id-auth/identify.jd b/docs/html/training/id-auth/identify.jd index 4c399f9abf39..db9ab3a671e5 100644 --- a/docs/html/training/id-auth/identify.jd +++ b/docs/html/training/id-auth/identify.jd @@ -15,7 +15,8 @@ next.link=authenticate.html <ol> <li><a href="#ForYou">Determine if AccountManager is for You</a></li> <li><a href="#TaskTwo">Decide What Type of Account to Use</a></li> - <li><a href="#QueryAccounts">Query the user for an Account</a></li> + <li><a href="#GetPermission">Request GET_ACCOUNT permission</a></li> + <li><a href="#TaskFive">Query AccountManager for a List of Accounts</a></li> <li><a href="#IdentifyUser">Use the Account Object to Personalize Your App</a></li> <li><a href="#IdIsEnough">Decide Whether an Account Name is Enough</a></li> </ol> @@ -70,46 +71,48 @@ UI.</p> <h2 id="TaskTwo">Decide What Type of Account to Use</h2> <p>Android devices can store multiple accounts from many different providers. -When you query {@link android.accounts.AccountManager} for account names, you -can choose to filter by account type. The account type is a string that -uniquely identifies the entity that issued the account. For instance, Google -accounts have type "com.google," while Twitter uses -"com.twitter.android.auth.login."</p> +When you query {@link android.accounts.AccountManager} for account names, you can choose to filter +by +account type. The account type is a string that uniquely identifies the entity +that issued the account. For instance, Google accounts have type "com.google," +while Twitter uses "com.twitter.android.auth.login."</p> -<h2 id="QueryAccounts">Query the user for an Account</h2> -<p>Once an account type has been determined, you can prompt the user with an -account chooser as follows: +<h2 id="GetPermission">Request GET_ACCOUNT permission</h2> + +<p>In order to get a list of accounts on the device, your app needs the {@link +android.Manifest.permission#GET_ACCOUNTS} +permission. Add a <a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">{@code +<uses-permission>}</a> tag in your manifest file to request +this permission:</p> <pre> -AccountManager am = AccountManager.get(this); // "this" reference the current Context -Intent chooserIntent = am.newChooseAccountIntent( - null, // currently select account - null, // list of accounts that are allowed to be shown - new String[] { "com.google" }, // Only allow the user to select Google accounts - false, - null, // description text - null, // add account auth token type - null, // required features for added accounts - null); // options for adding an account -this.startActivityForResult(chooserIntent, MY_REQUEST_CODE); +<manifest ... > + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + ... +</manifest> </pre> -<p>Once the chooser intent is started, the user will be presented with a list of -appropriately typed accounts. From this list they will select one which will be -returned to your app upon onActivityResult as follows: + +<h2 id="TaskFive">Query AccountManager for a List of Accounts</h2> + +<p>Once you decide what account type you're interested in, you need to query for accounts of that +type. Get an instance of {@link android.accounts.AccountManager} by calling {@link +android.accounts.AccountManager#get(android.content.Context) AccountManager.get()}. Then use that +instance to call {@link android.accounts.AccountManager#getAccountsByType(java.lang.String) +getAccountsByType()}.</p> <pre> -protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == MY_REQUEST_CODE && resultCode == RESULT_OK) { - String name = data.getStringExtra(AccountManage.KEY_ACCOUNT_NAME); - String type = data.getStringExtra(AccountManage.KEY_ACCOUNT_TYPE); - Account selectedAccount = new Account(name, type); - doSomethingWithSelectedAccount(selectedAccount); - } -} +AccountManager am = AccountManager.get(this); // "this" references the current Context + +Account[] accounts = am.getAccountsByType("com.google"); </pre> +<p>This returns an array of {@link android.accounts.Account} objects. If there's more than one +{@link android.accounts.Account} in +the array, you should present a dialog asking the user to select one.</p> + + <h2 id="IdentifyUser">Use the Account Object to Personalize Your App</h2> <p>The {@link android.accounts.Account} object contains an account name, which for Google accounts diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 1857345968fd..0a3e27e94c06 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -19,6 +19,10 @@ import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.Animator.AnimatorListener; +import android.animation.PropertyValuesHolder; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ObjectAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; @@ -34,14 +38,23 @@ import android.graphics.Rect; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; +import android.util.LongArray; +import android.util.PathParser; +import android.util.TimeUtils; +import android.view.Choreographer; +import android.view.DisplayListCanvas; +import android.view.RenderNode; +import android.view.RenderNodeAnimatorSetHelper; import android.view.View; import com.android.internal.R; +import com.android.internal.util.VirtualRefBasePtr; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -138,7 +151,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false; /** Local, mutable animator set. */ - private final AnimatorSet mAnimatorSet = new AnimatorSet(); + private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator(); /** * The resources against which this drawable was created. Used to attempt @@ -200,6 +213,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @Override public void draw(Canvas canvas) { + if (canvas.isHardwareAccelerated()) { + mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas); + } mAnimatedVectorState.mVectorDrawable.draw(canvas); if (isStarted()) { invalidateSelf(); @@ -582,9 +598,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { * Resets the AnimatedVectorDrawable to the start state as specified in the animators. */ public void reset() { - // TODO: Use reverse or seek to implement reset, when AnimatorSet supports them. - start(); - mAnimatorSet.cancel(); + mAnimatorSet.reset(); + invalidateSelf(); } @Override @@ -603,8 +618,12 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { @NonNull private void ensureAnimatorSet() { if (!mHasAnimatorSet) { - mAnimatedVectorState.prepareLocalAnimators(mAnimatorSet, mRes); + // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly + // with a list of LocalAnimators. + AnimatorSet set = new AnimatorSet(); + mAnimatedVectorState.prepareLocalAnimators(set, mRes); mHasAnimatorSet = true; + mAnimatorSet.initWithAnimatorSet(set); mRes = null; } } @@ -694,13 +713,13 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } }; } - mAnimatorSet.addListener(mAnimatorListener); + mAnimatorSet.setListener(mAnimatorListener); } // A helper function to clean up the animator listener in the mAnimatorSet. private void removeAnimatorSetListener() { if (mAnimatorListener != null) { - mAnimatorSet.removeListener(mAnimatorListener); + mAnimatorSet.removeListener(); mAnimatorListener = null; } } @@ -730,4 +749,406 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { mAnimationCallbacks.clear(); } -}
\ No newline at end of file + /** + * @hide + */ + public static class VectorDrawableAnimator { + private AnimatorListener mListener = null; + private final LongArray mStartDelays = new LongArray(); + private PropertyValuesHolder.PropertyValues mTmpValues = + new PropertyValuesHolder.PropertyValues(); + private long mSetPtr = 0; + private boolean mContainsSequentialAnimators = false; + private boolean mStarted = false; + private boolean mInitialized = false; + private boolean mAnimationPending = false; + private boolean mIsReversible = false; + // TODO: Consider using NativeAllocationRegistery to track native allocation + private final VirtualRefBasePtr mSetRefBasePtr; + private WeakReference<RenderNode> mTarget = null; + private WeakReference<RenderNode> mLastSeenTarget = null; + + + VectorDrawableAnimator() { + mSetPtr = nCreateAnimatorSet(); + // Increment ref count on native AnimatorSet, so it doesn't get released before Java + // side is done using it. + mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr); + } + + private void initWithAnimatorSet(AnimatorSet set) { + if (mInitialized) { + // Already initialized + throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " + + "re-initialized"); + } + parseAnimatorSet(set, 0); + mInitialized = true; + + // Check reversible. + if (mContainsSequentialAnimators) { + mIsReversible = false; + } else { + // Check if there's any start delay set on child + for (int i = 0; i < mStartDelays.size(); i++) { + if (mStartDelays.get(i) > 0) { + mIsReversible = false; + return; + } + } + } + mIsReversible = true; + } + + private void parseAnimatorSet(AnimatorSet set, long startTime) { + ArrayList<Animator> animators = set.getChildAnimations(); + + boolean playTogether = set.shouldPlayTogether(); + // Convert AnimatorSet to VectorDrawableAnimator + for (int i = 0; i < animators.size(); i++) { + Animator animator = animators.get(i); + // Here we only support ObjectAnimator + if (animator instanceof AnimatorSet) { + parseAnimatorSet((AnimatorSet) animator, startTime); + } else if (animator instanceof ObjectAnimator) { + createRTAnimator((ObjectAnimator) animator, startTime); + } // ignore ValueAnimators and others because they don't directly modify VD + // therefore will be useless to AVD. + + if (!playTogether) { + // Assume not play together means play sequentially + startTime += animator.getTotalDuration(); + mContainsSequentialAnimators = true; + } + } + } + + // TODO: This method reads animation data from already parsed Animators. We need to move + // this step further up the chain in the parser to avoid the detour. + private void createRTAnimator(ObjectAnimator animator, long startTime) { + PropertyValuesHolder[] values = animator.getValues(); + Object target = animator.getTarget(); + if (target instanceof VectorDrawable.VGroup) { + createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target, + startTime); + } else if (target instanceof VectorDrawable.VPath) { + for (int i = 0; i < values.length; i++) { + values[i].getPropertyValues(mTmpValues); + if (mTmpValues.endValue instanceof PathParser.PathData && + mTmpValues.propertyName.equals("pathData")) { + createRTAnimatorForPath(animator, (VectorDrawable.VPath) target, + startTime); + } else if (target instanceof VectorDrawable.VFullPath) { + createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target, + startTime); + } else { + throw new IllegalArgumentException("ClipPath only supports PathData " + + "property"); + } + + } + } else if (target instanceof VectorDrawable.VectorDrawableState) { + createRTAnimatorForRootGroup(values, animator, + (VectorDrawable.VectorDrawableState) target, startTime); + } else { + // Should never get here + throw new UnsupportedOperationException("Target should be either VGroup, VPath, " + + "or ConstantState, " + target.getClass() + " is not supported"); + } + } + + private void createRTAnimatorForGroup(PropertyValuesHolder[] values, + ObjectAnimator animator, VectorDrawable.VGroup target, + long startTime) { + + long nativePtr = target.getNativePtr(); + int propertyId; + for (int i = 0; i < values.length; i++) { + // TODO: We need to support the rare case in AVD where no start value is provided + values[i].getPropertyValues(mTmpValues); + propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName); + if (mTmpValues.type != Float.class && mTmpValues.type != float.class) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.e(LOGTAG, "Unsupported type: " + + mTmpValues.type + ". Only float value is supported for Groups."); + } + continue; + } + if (propertyId < 0) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.e(LOGTAG, "Unsupported property: " + + mTmpValues.propertyName + " for Vector Drawable Group"); + } + continue; + } + long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId, + (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); + if (mTmpValues.dataSource != null) { + float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator + .getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } + createNativeChildAnimator(propertyPtr, startTime, animator); + } + } + private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target, + long startTime) { + + long nativePtr = target.getNativePtr(); + long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue) + .getNativePtr(); + long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue) + .getNativePtr(); + long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr, + endPathDataPtr); + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + private void createRTAnimatorForFullPath(ObjectAnimator animator, + VectorDrawable.VFullPath target, long startTime) { + + int propertyId = target.getPropertyIndex(mTmpValues.propertyName); + long propertyPtr; + long nativePtr = target.getNativePtr(); + if (mTmpValues.type == Float.class || mTmpValues.type == float.class) { + if (propertyId < 0) { + throw new IllegalArgumentException("Property: " + mTmpValues + .propertyName + " is not supported for FullPath"); + } + propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId, + (Float) mTmpValues.startValue, (Float) mTmpValues.endValue); + + } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) { + propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId, + (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue); + } else { + throw new UnsupportedOperationException("Unsupported type: " + + mTmpValues.type + ". Only float, int or PathData value is " + + "supported for Paths."); + } + if (mTmpValues.dataSource != null) { + float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator + .getDuration()); + nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length); + } + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values, + ObjectAnimator animator, VectorDrawable.VectorDrawableState target, + long startTime) { + long nativePtr = target.getNativeRenderer(); + if (!animator.getPropertyName().equals("alpha")) { + throw new UnsupportedOperationException("Only alpha is supported for root " + + "group"); + } + Float startValue = null; + Float endValue = null; + for (int i = 0; i < values.length; i++) { + values[i].getPropertyValues(mTmpValues); + if (mTmpValues.propertyName.equals("alpha")) { + startValue = (Float) mTmpValues.startValue; + endValue = (Float) mTmpValues.endValue; + break; + } + } + if (startValue == null && endValue == null) { + throw new UnsupportedOperationException("No alpha values are specified"); + } + long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue); + createNativeChildAnimator(propertyPtr, startTime, animator); + } + + // These are the data points that define the value of the animating properties. + // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1] + // a point on the path corresponds to the values of translateX and translateY. + // TODO: (Optimization) We should pass the path down in native and chop it into segments + // in native. + private static float[] createDataPoints( + PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) { + long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos(); + int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS); + int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs); + float values[] = new float[numAnimFrames]; + float lastFrame = numAnimFrames - 1; + for (int i = 0; i < numAnimFrames; i++) { + float fraction = i / lastFrame; + values[i] = (Float) dataSource.getValueAtFraction(fraction); + } + return values; + } + + private void createNativeChildAnimator(long propertyPtr, long extraDelay, + ObjectAnimator animator) { + long duration = animator.getDuration(); + int repeatCount = animator.getRepeatCount(); + long startDelay = extraDelay + animator.getStartDelay(); + TimeInterpolator interpolator = animator.getInterpolator(); + long nativeInterpolator = + RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration); + + startDelay *= ValueAnimator.getDurationScale(); + duration *= ValueAnimator.getDurationScale(); + + mStartDelays.add(startDelay); + nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration, + repeatCount); + } + + /** + * Holds a weak reference to the target that was last seen (through the DisplayListCanvas + * in the last draw call), so that when animator set needs to start, we can add the animator + * to the last seen RenderNode target and start right away. + */ + protected void recordLastSeenTarget(DisplayListCanvas canvas) { + if (mAnimationPending) { + mLastSeenTarget = new WeakReference<RenderNode>( + RenderNodeAnimatorSetHelper.getTarget(canvas)); + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "Target is set in the next frame"); + } + mAnimationPending = false; + start(); + } else { + mLastSeenTarget = new WeakReference<RenderNode>( + RenderNodeAnimatorSetHelper.getTarget(canvas)); + } + + } + + private boolean setTarget(RenderNode node) { + if (mTarget != null && mTarget.get() != null) { + // TODO: Maybe we want to support target change. + throw new IllegalStateException("Target already set!"); + } + + node.addAnimator(this); + mTarget = new WeakReference<RenderNode>(node); + return true; + } + + private boolean useLastSeenTarget() { + if (mLastSeenTarget != null && mLastSeenTarget.get() != null) { + setTarget(mLastSeenTarget.get()); + return true; + } + return false; + } + + public void start() { + if (!mInitialized) { + return; + } + + if (mStarted) { + return; + } + + if (!useLastSeenTarget()) { + mAnimationPending = true; + return; + } + + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java"); + } + + nStart(mSetPtr, this); + if (mListener != null) { + mListener.onAnimationStart(null); + } + mStarted = true; + } + + public void end() { + if (mInitialized && mStarted) { + nEnd(mSetPtr); + onAnimationEnd(); + } + } + + void reset() { + if (!mInitialized) { + return; + } + // TODO: Need to implement reset. + Log.w(LOGTAG, "Reset is yet to be implemented"); + nReset(mSetPtr); + } + + // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential + // animators or when the animator set has a start delay + void reverse() { + if (!mIsReversible) { + return; + } + // TODO: Need to support reverse (non-public API) + Log.w(LOGTAG, "Reverse is yet to be implemented"); + nReverse(mSetPtr, this); + } + + public long getAnimatorNativePtr() { + return mSetPtr; + } + + boolean canReverse() { + return mIsReversible; + } + + boolean isStarted() { + return mStarted; + } + + boolean isRunning() { + if (!mInitialized) { + return false; + } + return mStarted; + } + + void setListener(AnimatorListener listener) { + mListener = listener; + } + + void removeListener() { + mListener = null; + } + + private void onAnimationEnd() { + mStarted = false; + if (mListener != null) { + mListener.onAnimationEnd(null); + } + mTarget = null; + } + + // onFinished: should be called from native + private static void callOnFinished(VectorDrawableAnimator set) { + if (DBG_ANIMATION_VECTOR_DRAWABLE) { + Log.d(LOGTAG, "on finished called from native"); + } + set.onAnimationEnd(); + } + } + + private static native long nCreateAnimatorSet(); + private static native void nAddAnimator(long setPtr, long propertyValuesHolder, + long nativeInterpolator, long startDelay, long duration, int repeatCount); + + private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId, + float startValue, float endValue); + + private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr, + long endValuePtr); + private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId, + int startValue, int endValue); + private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId, + float startValue, float endValue); + private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue, + float endValue); + private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length); + private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set); + private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set); + private static native void nEnd(long animatorSetPtr); + private static native void nReset(long animatorSetPtr); +} diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 1fc1b83f7a6c..f4bbc8c43d08 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -39,6 +39,7 @@ import android.util.PathParser; import android.util.Xml; import com.android.internal.R; +import com.android.internal.util.VirtualRefBasePtr; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -47,6 +48,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashMap; import java.util.Stack; /** @@ -522,13 +524,13 @@ public class VectorDrawable extends Drawable { public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererPtr != 0) { + if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) { // This VD has been used to display other VD resource content, clean up. mVectorState.mRootGroup = new VGroup(); - if (mVectorState.mNativeRendererPtr != 0) { - nDestroyRenderer(mVectorState.mNativeRendererPtr); + if (mVectorState.mNativeRendererRefBase != null) { + mVectorState.mNativeRendererRefBase.release(); } - mVectorState.mNativeRendererPtr = nCreateRenderer(mVectorState.mRootGroup.mNativePtr); + mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr); } final VectorDrawableState state = mVectorState; state.setDensity(Drawable.resolveDensity(r, 0)); @@ -707,7 +709,7 @@ public class VectorDrawable extends Drawable { return mVectorState.mAutoMirrored; } - private static class VectorDrawableState extends ConstantState { + static class VectorDrawableState extends ConstantState { // Variables below need to be copied (deep copy if applicable) for mutation. int[] mThemeAttrs; int mChangingConfigurations; @@ -722,7 +724,7 @@ public class VectorDrawable extends Drawable { Insets mOpticalInsets = Insets.NONE; String mRootName = null; VGroup mRootGroup; - long mNativeRendererPtr; + VirtualRefBasePtr mNativeRendererRefBase = null; int mDensity = DisplayMetrics.DENSITY_DEFAULT; final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>(); @@ -743,7 +745,7 @@ public class VectorDrawable extends Drawable { mTintMode = copy.mTintMode; mAutoMirrored = copy.mAutoMirrored; mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap); - mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr); + createNativeRenderer(mRootGroup.mNativePtr); mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; @@ -758,18 +760,15 @@ public class VectorDrawable extends Drawable { } } - @Override - public void finalize() throws Throwable { - if (mNativeRendererPtr != 0) { - nDestroyRenderer(mNativeRendererPtr); - mNativeRendererPtr = 0; - } - super.finalize(); + private void createNativeRenderer(long rootGroupPtr) { + mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr)); } - long getNativeRenderer() { - return mNativeRendererPtr; + if (mNativeRendererRefBase == null) { + return 0; + } + return mNativeRendererRefBase.get(); } public boolean canReuseCache() { @@ -808,7 +807,7 @@ public class VectorDrawable extends Drawable { public VectorDrawableState() { mRootGroup = new VGroup(); - mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr); + createNativeRenderer(mRootGroup.mNativePtr); } @Override @@ -872,16 +871,16 @@ public class VectorDrawable extends Drawable { * has changed. */ public boolean setAlpha(float alpha) { - return nSetRootAlpha(mNativeRendererPtr, alpha); + return nSetRootAlpha(mNativeRendererRefBase.get(), alpha); } @SuppressWarnings("unused") public float getAlpha() { - return nGetRootAlpha(mNativeRendererPtr); + return nGetRootAlpha(mNativeRendererRefBase.get()); } } - private static class VGroup implements VObject { + static class VGroup implements VObject { private static final int ROTATE_INDEX = 0; private static final int PIVOT_X_INDEX = 1; private static final int PIVOT_Y_INDEX = 2; @@ -891,6 +890,28 @@ public class VectorDrawable extends Drawable { private static final int TRANSLATE_Y_INDEX = 6; private static final int TRANSFORM_PROPERTY_COUNT = 7; + private static final HashMap<String, Integer> sPropertyMap = + new HashMap<String, Integer>() { + { + put("translateX", TRANSLATE_X_INDEX); + put("translateY", TRANSLATE_Y_INDEX); + put("scaleX", SCALE_X_INDEX); + put("scaleY", SCALE_Y_INDEX); + put("pivotX", PIVOT_X_INDEX); + put("pivotY", PIVOT_Y_INDEX); + put("rotation", ROTATE_INDEX); + } + }; + + static int getPropertyIndex(String propertyName) { + if (sPropertyMap.containsKey(propertyName)) { + return sPropertyMap.get(propertyName); + } else { + // property not found + return -1; + } + } + // Temp array to store transform values obtained from native. private float[] mTransform; ///////////////////////////////////////////////////// @@ -1149,7 +1170,7 @@ public class VectorDrawable extends Drawable { /** * Common Path information for clip path and normal path. */ - private static abstract class VPath implements VObject { + static abstract class VPath implements VObject { protected PathParser.PathData mPathData = null; String mPathName; @@ -1260,7 +1281,7 @@ public class VectorDrawable extends Drawable { /** * Normal path, which contains all the fill / paint information. */ - private static class VFullPath extends VPath { + static class VFullPath extends VPath { private static final int STROKE_WIDTH_INDEX = 0; private static final int STROKE_COLOR_INDEX = 1; private static final int STROKE_ALPHA_INDEX = 2; @@ -1274,6 +1295,20 @@ public class VectorDrawable extends Drawable { private static final int STROKE_MITER_LIMIT_INDEX = 10; private static final int TOTAL_PROPERTY_COUNT = 11; + private final static HashMap<String, Integer> sPropertyMap + = new HashMap<String, Integer> () { + { + put("strokeWidth", STROKE_WIDTH_INDEX); + put("strokeColor", STROKE_COLOR_INDEX); + put("strokeAlpha", STROKE_ALPHA_INDEX); + put("fillColor", FILL_COLOR_INDEX); + put("fillAlpha", FILL_ALPHA_INDEX); + put("trimPathStart", TRIM_PATH_START_INDEX); + put("trimPathEnd", TRIM_PATH_END_INDEX); + put("trimPathOffset", TRIM_PATH_OFFSET_INDEX); + } + }; + // Temp array to store property data obtained from native getter. private byte[] mPropertyData; ///////////////////////////////////////////////////// @@ -1297,6 +1332,14 @@ public class VectorDrawable extends Drawable { mFillColors = copy.mFillColors; } + int getPropertyIndex(String propertyName) { + if (!sPropertyMap.containsKey(propertyName)) { + return -1; + } else { + return sPropertyMap.get(propertyName); + } + } + @Override public boolean onStateChange(int[] stateSet) { boolean changed = false; @@ -1595,7 +1638,6 @@ public class VectorDrawable extends Drawable { } private static native long nCreateRenderer(long rootGroupPtr); - private static native void nDestroyRenderer(long rendererPtr); private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth, float viewportHeight); private static native boolean nSetRootAlpha(long rendererPtr, float alpha); diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 587a366730c2..6988b0294539 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -78,6 +78,8 @@ hwui_src_files := \ Program.cpp \ ProgramCache.cpp \ Properties.cpp \ + PropertyValuesHolder.cpp \ + PropertyValuesAnimatorSet.cpp \ RenderBufferCache.cpp \ RenderNode.cpp \ RenderProperties.cpp \ diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 5ca2a2fa37ab..7bd2b24bf56b 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -90,6 +90,9 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { doSetStartValue(getValue(mTarget)); } if (mStagingPlayState > mPlayState) { + if (mStagingPlayState == PlayState::Restarted) { + mStagingPlayState = PlayState::Running; + } mPlayState = mStagingPlayState; // Oh boy, we're starting! Man the battle stations! if (mPlayState == PlayState::Running) { @@ -131,6 +134,11 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { return true; } + // This should be set before setValue() so animators can query this time when setValue + // is called. + nsecs_t currentFrameTime = context.frameTimeMs(); + onPlayTimeChanged(currentFrameTime - mStartTime); + // If BaseRenderNodeAnimator is handling the delay (not typical), then // because the staging properties reflect the final value, we always need // to call setValue even if the animation isn't yet running or is still @@ -141,8 +149,9 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { } float fraction = 1.0f; + if (mPlayState == PlayState::Running && mDuration > 0) { - fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration; + fraction = (float)(currentFrameTime - mStartTime) / mDuration; } if (fraction >= 1.0f) { fraction = 1.0f; diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index aea95bfc1c0e..2c9c9c3fe0f9 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -59,7 +59,13 @@ public: mMayRunAsync = mayRunAsync; } bool mayRunAsync() { return mMayRunAsync; } - ANDROID_API void start() { mStagingPlayState = PlayState::Running; onStagingPlayStateChanged(); } + ANDROID_API void start() { + if (mStagingPlayState == PlayState::NotStarted) { + mStagingPlayState = PlayState::Running; + } else { + mStagingPlayState = PlayState::Restarted; + } + onStagingPlayStateChanged(); } ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); } void attach(RenderNode* target); @@ -77,10 +83,27 @@ public: void forceEndNow(AnimationContext& context); protected: + // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI + // thread and Render Thread animation state, respectively. + // From the UI thread, mStagingPlayState transition looks like + // NotStarted -> Running -> Finished + // ^ | + // | | + // Restarted <------ + // Note: For mStagingState, the Finished state (optional) is only set when the animation is + // terminated by user. + // + // On Render Thread, mPlayState transition: + // NotStart -> Running -> Finished + // ^ | + // | | + // ------------- + enum class PlayState { NotStarted, Running, Finished, + Restarted, }; BaseRenderNodeAnimator(float finalValue); @@ -93,6 +116,7 @@ protected: void callOnFinishedListener(AnimationContext& context); virtual void onStagingPlayStateChanged() {} + virtual void onPlayTimeChanged(nsecs_t playTime) {} RenderNode* mTarget; diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index ca1f8f94f382..2184755d218a 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -728,23 +728,34 @@ void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const Textur } void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { + // Note that we don't use op->paint in this function - it's never set on a LayerOp OffscreenBuffer* buffer = *op.layerHandle; - // Note that we don't use op->paint here - it's never set on a LayerOp - float layerAlpha = op.alpha * state.alpha; - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount) - .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top, - Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) - .build(); - renderer.renderGlop(state, glop); + if (CC_UNLIKELY(!buffer)) { + // Layer was not allocated, which can occur if there were no draw ops inside. We draw the + // equivalent by drawing a rect with the same layer properties (alpha/xfer/filter). + SkPaint paint; + paint.setAlpha(op.alpha * 255); + paint.setXfermodeMode(op.mode); + paint.setColorFilter(op.colorFilter); + RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint); + BakedOpDispatcher::onRectOp(renderer, rectOp, state); + } else { + float layerAlpha = op.alpha * state.alpha; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount) + .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top, + Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) + .build(); + renderer.renderGlop(state, glop); - if (op.destroy) { - renderer.renderState().layerPool().putOrDelete(buffer); + if (op.destroy) { + renderer.renderState().layerPool().putOrDelete(buffer); + } } } diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index 9dfe454c7bc1..d7e2f09d4ba1 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -43,6 +43,13 @@ typedef uint32_t Flags; } // namespace SaveFlags +namespace uirenderer { +namespace VectorDrawable { +class Tree; +}; +}; +typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; + class ANDROID_API Canvas { public: virtual ~Canvas() {}; @@ -185,6 +192,11 @@ public: */ virtual bool drawTextAbsolutePos() const = 0; + /** + * Draws a VectorDrawable onto the canvas. + */ + virtual void drawVectorDrawable(VectorDrawableRoot* tree); + protected: void drawTextDecorations(float x, float y, float length, const SkPaint& paint); }; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 384e64d7700f..5366127092d4 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -16,11 +16,12 @@ #include "DisplayListCanvas.h" -#include "ResourceCache.h" #include "DeferredDisplayList.h" #include "DeferredLayerUpdater.h" #include "DisplayListOp.h" +#include "ResourceCache.h" #include "RenderNode.h" +#include "VectorDrawable.h" #include "utils/PaintUtils.h" #include <SkCamera.h> @@ -412,6 +413,16 @@ void DisplayListCanvas::drawPoints(const float* points, int count, const SkPaint addDrawOp(new (alloc()) DrawPointsOp(points, count, refPaint(&paint))); } +void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + mDisplayList->ref(tree); + const SkBitmap& bitmap = tree->getBitmapUpdateIfDirty(); + SkPaint* paint = tree->getPaint(); + const SkRect bounds = tree->getBounds(); + addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap), + 0, 0, bitmap.width(), bitmap.height(), + bounds.left(), bounds.top(), bounds.right(), bounds.bottom(), refPaint(paint))); +} + void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) { if (!glyphs || count <= 0) return; diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h index f1cfa08b3ac2..d41fff425e81 100644 --- a/libs/hwui/DisplayListCanvas.h +++ b/libs/hwui/DisplayListCanvas.h @@ -209,6 +209,8 @@ public: float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) override; + virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; + // Text virtual void drawText(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, @@ -217,7 +219,6 @@ public: float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return false; } - private: CanvasState mState; diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index 9528587ed860..57e5b9d8735a 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -19,6 +19,7 @@ #include "Canvas.h" #include "LayerUpdateQueue.h" #include "RenderNode.h" +#include "VectorDrawable.h" #include "renderstate/OffscreenBufferPool.h" #include "utils/FatVector.h" #include "utils/PaintUtils.h" @@ -543,6 +544,18 @@ void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) { currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); } +void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) { + const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty(); + SkPaint* paint = op.vectorDrawable->getPaint(); + const BitmapRectOp* resolvedOp = new (mAllocator) BitmapRectOp(op.unmappedBounds, + op.localMatrix, + op.localClip, + paint, + &bitmap, + Rect(bitmap.width(), bitmap.height())); + deferBitmapRectOp(*resolvedOp); +} + void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) { // allocate a temporary oval op (with mAllocator, so it persists until render), so the // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h index 5df2e46078b8..f44306a931bf 100644 --- a/libs/hwui/FrameBuilder.h +++ b/libs/hwui/FrameBuilder.h @@ -124,7 +124,8 @@ public: layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); GL_CHECKPOINT(MODERATE); renderer.endLayer(); - } else if (!layer.empty()) { // save layer - skip entire layer if empty + } else if (!layer.empty()) { + // save layer - skip entire layer if empty (in which case, LayerOp has null layer). layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height); GL_CHECKPOINT(MODERATE); layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp new file mode 100644 index 000000000000..eca1afcc54dc --- /dev/null +++ b/libs/hwui/PropertyValuesAnimatorSet.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 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 "PropertyValuesAnimatorSet.h" +#include "RenderNode.h" + +namespace android { +namespace uirenderer { + +void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, + Interpolator* interpolator, nsecs_t startDelay, + nsecs_t duration, int repeatCount) { + + PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder, + interpolator, startDelay, duration, repeatCount); + mAnimators.emplace_back(animator); + setListener(new PropertyAnimatorSetListener(this)); +} + +PropertyValuesAnimatorSet::PropertyValuesAnimatorSet() + : BaseRenderNodeAnimator(1.0f) { + setStartValue(0); + mLastFraction = 0.0f; + setInterpolator(new LinearInterpolator()); +} + +void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) { + if (mOneShotListener.get()) { + mOneShotListener->onAnimationFinished(animator); + mOneShotListener = nullptr; + } +} + +float PropertyValuesAnimatorSet::getValue(RenderNode* target) const { + return mLastFraction; +} + +void PropertyValuesAnimatorSet::setValue(RenderNode* target, float value) { + mLastFraction = value; +} + +void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) { + for (size_t i = 0; i < mAnimators.size(); i++) { + mAnimators[i]->setCurrentPlayTime(playTime); + } +} + +void PropertyValuesAnimatorSet::reset() { + // TODO: implement reset through adding a play state because we need to support reset() even + // during an animation run. +} + +void PropertyValuesAnimatorSet::start(AnimationListener* listener) { + init(); + mOneShotListener = listener; + BaseRenderNodeAnimator::start(); +} + +void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) { +// TODO: implement reverse +} + +void PropertyValuesAnimatorSet::init() { + if (mInitialized) { + return; + } + nsecs_t maxDuration = 0; + for (size_t i = 0; i < mAnimators.size(); i++) { + if (maxDuration < mAnimators[i]->getTotalDuration()) { + maxDuration = mAnimators[i]->getTotalDuration(); + } + } + mDuration = maxDuration; + mInitialized = true; +} + +uint32_t PropertyValuesAnimatorSet::dirtyMask() { + return RenderNode::DISPLAY_LIST; +} + +PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, + nsecs_t startDelay, nsecs_t duration, int repeatCount) + : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay), + mDuration(duration) { + if (repeatCount < 0) { + mRepeatCount = UINT32_MAX; + } else { + mRepeatCount = repeatCount; + } + mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay; +} + +void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) { + if (playTime >= mStartDelay && playTime < mTotalDuration) { + nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration; + mLatestFraction = currentIterationPlayTime / (float) mDuration; + } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) { + mLatestFraction = 1.0f; + } else { + return; + } + + setFraction(mLatestFraction); +} + +void PropertyAnimator::setFraction(float fraction) { + float interpolatedFraction = mInterpolator->interpolate(mLatestFraction); + mPropertyValuesHolder->setFraction(interpolatedFraction); +} + +void PropertyAnimatorSetListener::onAnimationFinished(BaseRenderNodeAnimator* animator) { + mSet->onFinished(animator); +} + +} +} diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h new file mode 100644 index 000000000000..4c7ce528bb20 --- /dev/null +++ b/libs/hwui/PropertyValuesAnimatorSet.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "Animator.h" +#include "PropertyValuesHolder.h" +#include "Interpolator.h" + +namespace android { +namespace uirenderer { + +class PropertyAnimator { +public: + PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay, + nsecs_t duration, int repeatCount); + void setCurrentPlayTime(nsecs_t playTime); + nsecs_t getTotalDuration() { + return mTotalDuration; + } + void setFraction(float fraction); + +private: + std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder; + std::unique_ptr<Interpolator> mInterpolator; + nsecs_t mStartDelay; + nsecs_t mDuration; + uint32_t mRepeatCount; + nsecs_t mTotalDuration; + float mLatestFraction = 0.0f; +}; + +class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator { +public: + friend class PropertyAnimatorSetListener; + PropertyValuesAnimatorSet(); + + void start(AnimationListener* listener); + void reverse(AnimationListener* listener); + void reset(); + + void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, + Interpolator* interpolators, int64_t startDelays, + nsecs_t durations, int repeatCount); + virtual uint32_t dirtyMask(); + +protected: + virtual float getValue(RenderNode* target) const override; + virtual void setValue(RenderNode* target, float value) override; + virtual void onPlayTimeChanged(nsecs_t playTime) override; + +private: + void init(); + void onFinished(BaseRenderNodeAnimator* animator); + // Listener set from outside + sp<AnimationListener> mOneShotListener; + std::vector< std::unique_ptr<PropertyAnimator> > mAnimators; + float mLastFraction = 0.0f; + bool mInitialized = false; +}; + +class PropertyAnimatorSetListener : public AnimationListener { +public: + PropertyAnimatorSetListener(PropertyValuesAnimatorSet* set) : mSet(set) {} + virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) override; + +private: + PropertyValuesAnimatorSet* mSet; +}; + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp new file mode 100644 index 000000000000..8f837f6048d6 --- /dev/null +++ b/libs/hwui/PropertyValuesHolder.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 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 "PropertyValuesHolder.h" + +#include "utils/VectorDrawableUtils.h" + +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +using namespace VectorDrawable; + +float PropertyValuesHolder::getValueFromData(float fraction) { + if (mDataSource.size() == 0) { + LOG_ALWAYS_FATAL("No data source is defined"); + return 0; + } + if (fraction <= 0.0f) { + return mDataSource.front(); + } + if (fraction >= 1.0f) { + return mDataSource.back(); + } + + fraction *= mDataSource.size() - 1; + int lowIndex = floor(fraction); + fraction -= lowIndex; + + float value = mDataSource[lowIndex] * (1.0f - fraction) + + mDataSource[lowIndex + 1] * fraction; + return value; +} + +void GroupPropertyValuesHolder::setFraction(float fraction) { + float animatedValue; + if (mDataSource.size() > 0) { + animatedValue = getValueFromData(fraction); + } else { + animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + } + mGroup->setPropertyValue(mPropertyId, animatedValue); +} + +inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { + return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); +} + +// TODO: Add a test for this +SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor, + float fraction) { + U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); + U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); + U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); + U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); + return SkColorSetARGB(alpha, red, green, blue); +} + +void FullPathColorPropertyValuesHolder::setFraction(float fraction) { + SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction); + mFullPath->setColorPropertyValue(mPropertyId, animatedValue); +} + +void FullPathPropertyValuesHolder::setFraction(float fraction) { + float animatedValue; + if (mDataSource.size() > 0) { + animatedValue = getValueFromData(fraction); + } else { + animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + } + mFullPath->setPropertyValue(mPropertyId, animatedValue); +} + +void PathDataPropertyValuesHolder::setFraction(float fraction) { + VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction); + mPath->setPathData(mPathData); +} + +void RootAlphaPropertyValuesHolder::setFraction(float fraction) { + float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + mTree->setRootAlpha(animatedValue); +} + +} // namepace uirenderer +} // namespace android diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h new file mode 100644 index 000000000000..b905faef104c --- /dev/null +++ b/libs/hwui/PropertyValuesHolder.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "VectorDrawable.h" + +#include <SkColor.h> + +namespace android { +namespace uirenderer { + +/** + * PropertyValues holder contains data needed to change a property of a Vector Drawable object. + * When a fraction in [0f, 1f] is provided, the holder will calculate an interpolated value based + * on its start and end value, and set the new value on the VectorDrawble's corresponding property. + */ +class ANDROID_API PropertyValuesHolder { +public: + virtual void setFraction(float fraction) = 0; + void setPropertyDataSource(float* dataSource, int length) { + mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length); + } + float getValueFromData(float fraction); + virtual ~PropertyValuesHolder() {} +protected: + std::vector<float> mDataSource; +}; + +class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder { +public: + GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue, + float endValue) + : mGroup(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue){ + } + void setFraction(float fraction) override; +private: + VectorDrawable::Group* mGroup; + int mPropertyId; + float mStartValue; + float mEndValue; +}; + +class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder { +public: + FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue, + int32_t endValue) + : mFullPath(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue) {}; + void setFraction(float fraction) override; + static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction); +private: + VectorDrawable::FullPath* mFullPath; + int mPropertyId; + int32_t mStartValue; + int32_t mEndValue; +}; + +class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder { +public: + FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue, + float endValue) + : mFullPath(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue) {}; + void setFraction(float fraction) override; +private: + VectorDrawable::FullPath* mFullPath; + int mPropertyId; + float mStartValue; + float mEndValue; +}; + +class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder { +public: + PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue, + PathData* endValue) + : mPath(ptr) + , mStartValue(*startValue) + , mEndValue(*endValue) {}; + void setFraction(float fraction) override; +private: + VectorDrawable::Path* mPath; + PathData mPathData; + PathData mStartValue; + PathData mEndValue; +}; + +class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder { +public: + RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue) + : mTree(tree) + , mStartValue(startValue) + , mEndValue(endValue) {} + void setFraction(float fraction) override; +private: + VectorDrawable::Tree* mTree; + float mStartValue; + float mEndValue; +}; +} +} diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index 593d690f2b43..bb26e2ec67a8 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -17,6 +17,7 @@ #ifndef ANDROID_HWUI_RECORDED_OP_H #define ANDROID_HWUI_RECORDED_OP_H +#include "RecordedOp.h" #include "font/FontUtil.h" #include "Matrix.h" #include "Rect.h" @@ -39,6 +40,10 @@ class OffscreenBuffer; class RenderNode; struct Vertex; +namespace VectorDrawable { +class Tree; +} + /** * Authoritative op list, used for generating the op ID enum, ID based LUTS, and * the functions to which they dispatch. Parameter macros are executed for each op, @@ -75,6 +80,7 @@ struct Vertex; PRE_RENDER_OP_FN(EndLayerOp) \ PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \ PRE_RENDER_OP_FN(EndUnclippedLayerOp) \ + PRE_RENDER_OP_FN(VectorDrawableOp) \ \ RENDER_ONLY_OP_FN(ShadowOp) \ RENDER_ONLY_OP_FN(LayerOp) \ @@ -325,6 +331,13 @@ struct RoundRectPropsOp : RecordedOp { const float* ry; }; +struct VectorDrawableOp : RecordedOp { + VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS) + : SUPER_PAINTLESS(VectorDrawableOp) + , vectorDrawable(tree) {} + VectorDrawable::Tree* vectorDrawable; +}; + /** * Real-time, dynamic-lit shadow. * diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 328e291a60af..abbd9c38d0ad 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -19,6 +19,7 @@ #include "DeferredLayerUpdater.h" #include "RecordedOp.h" #include "RenderNode.h" +#include "VectorDrawable.h" namespace android { namespace uirenderer { @@ -395,7 +396,6 @@ void RecordingCanvas::drawCircle( &x->value, &y->value, &radius->value)); } - void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { addOp(new (alloc()) OvalOp( Rect(left, top, right, bottom), @@ -422,6 +422,15 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { refPaint(&paint), refPath(&path))); } +void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + mDisplayList->ref(tree); + addOp(new (alloc()) VectorDrawableOp( + tree, + Rect(tree->getBounds()), + *(mState.currentSnapshot()->transform), + getRecordedClip())); +} + // Bitmap-based void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { save(SaveFlags::Matrix); diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 786f96e852ec..7c8ad8814d32 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -178,6 +178,8 @@ public: const uint16_t* indices, int indexCount, const SkPaint& paint) override { /* RecordingCanvas does not support drawVertices(); ignore */ } + virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; + // Bitmap-based virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override; virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 20e7c711a9ce..550995b2b9c6 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -27,6 +27,8 @@ #include <SkTLazy.h> #include <SkTemplates.h> +#include "VectorDrawable.h" + #include <memory> namespace android { @@ -136,6 +138,7 @@ public: float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return true; } + virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; private: struct SaveRec { @@ -713,6 +716,14 @@ void SkiaCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& chu NinePatch::Draw(mCanvas, bounds, bitmap, chunk, paint, nullptr); } +void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { + const SkBitmap& bitmap = vectorDrawable->getBitmapUpdateIfDirty(); + SkRect bounds = vectorDrawable->getBounds(); + drawBitmap(bitmap, 0, 0, bitmap.width(), bitmap.height(), + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, + vectorDrawable->getPaint()); +} + // ---------------------------------------------------------------------------- // Canvas draw operations: Text // ---------------------------------------------------------------------------- diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 1cf15ac01154..c72f87d7e481 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -138,18 +138,7 @@ void Path::setPath(const char* pathStr, size_t strLength) { } FullPath::FullPath(const FullPath& path) : Path(path) { - mStrokeWidth = path.mStrokeWidth; - mStrokeColor = path.mStrokeColor; - mStrokeAlpha = path.mStrokeAlpha; - mFillColor = path.mFillColor; - mFillAlpha = path.mFillAlpha; - mTrimPathStart = path.mTrimPathStart; - mTrimPathEnd = path.mTrimPathEnd; - mTrimPathOffset = path.mTrimPathOffset; - mStrokeMiterLimit = path.mStrokeMiterLimit; - mStrokeLineCap = path.mStrokeLineCap; - mStrokeLineJoin = path.mStrokeLineJoin; - + mProperties = path.mProperties; SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient); SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient); } @@ -159,7 +148,7 @@ const SkPath& FullPath::getUpdatedPath() { return mTrimmedSkPath; } Path::getUpdatedPath(); - if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) { + if (mProperties.trimPathStart != 0.0f || mProperties.trimPathEnd != 1.0f) { applyTrim(); return mTrimmedSkPath; } else { @@ -170,14 +159,14 @@ const SkPath& FullPath::getUpdatedPath() { void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha, SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) { - mStrokeWidth = strokeWidth; - mStrokeColor = strokeColor; - mStrokeAlpha = strokeAlpha; - mFillColor = fillColor; - mFillAlpha = fillAlpha; - mStrokeMiterLimit = strokeMiterLimit; - mStrokeLineCap = SkPaint::Cap(strokeLineCap); - mStrokeLineJoin = SkPaint::Join(strokeLineJoin); + mProperties.strokeWidth = strokeWidth; + mProperties.strokeColor = strokeColor; + mProperties.strokeAlpha = strokeAlpha; + mProperties.fillColor = fillColor; + mProperties.fillAlpha = fillAlpha; + mProperties.strokeMiterLimit = strokeMiterLimit; + mProperties.strokeLineCap = strokeLineCap; + mProperties.strokeLineJoin = strokeLineJoin; // If any trim property changes, mark trim dirty and update the trim path setTrimPathStart(trimPathStart); @@ -195,12 +184,12 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str // Draw path's fill, if fill color or gradient is valid bool needsFill = false; if (mFillGradient != nullptr) { - mPaint.setColor(applyAlpha(SK_ColorBLACK, mFillAlpha)); + mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.fillAlpha)); SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix); mPaint.setShader(newShader); needsFill = true; - } else if (mFillColor != SK_ColorTRANSPARENT) { - mPaint.setColor(applyAlpha(mFillColor, mFillAlpha)); + } else if (mProperties.fillColor != SK_ColorTRANSPARENT) { + mPaint.setColor(applyAlpha(mProperties.fillColor, mProperties.fillAlpha)); needsFill = true; } @@ -213,21 +202,21 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str // Draw path's stroke, if stroke color or gradient is valid bool needsStroke = false; if (mStrokeGradient != nullptr) { - mPaint.setColor(applyAlpha(SK_ColorBLACK, mStrokeAlpha)); + mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.strokeAlpha)); SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix); mPaint.setShader(newShader); needsStroke = true; - } else if (mStrokeColor != SK_ColorTRANSPARENT) { - mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha)); + } else if (mProperties.strokeColor != SK_ColorTRANSPARENT) { + mPaint.setColor(applyAlpha(mProperties.strokeColor, mProperties.strokeAlpha)); needsStroke = true; } if (needsStroke) { mPaint.setStyle(SkPaint::Style::kStroke_Style); mPaint.setAntiAlias(true); - mPaint.setStrokeJoin(mStrokeLineJoin); - mPaint.setStrokeCap(mStrokeLineCap); - mPaint.setStrokeMiter(mStrokeMiterLimit); - mPaint.setStrokeWidth(mStrokeWidth * strokeScale); + mPaint.setStrokeJoin(SkPaint::Join(mProperties.strokeLineJoin)); + mPaint.setStrokeCap(SkPaint::Cap(mProperties.strokeLineCap)); + mPaint.setStrokeMiter(mProperties.strokeMiterLimit); + mPaint.setStrokeWidth(mProperties.strokeWidth * strokeScale); outCanvas->drawPath(renderPath, mPaint); } } @@ -236,14 +225,14 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str * Applies trimming to the specified path. */ void FullPath::applyTrim() { - if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) { + if (mProperties.trimPathStart == 0.0f && mProperties.trimPathEnd == 1.0f) { // No trimming necessary. return; } SkPathMeasure measure(mSkPath, false); float len = SkScalarToFloat(measure.getLength()); - float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f); - float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f); + float start = len * fmod((mProperties.trimPathStart + mProperties.trimPathOffset), 1.0f); + float end = len * fmod((mProperties.trimPathEnd + mProperties.trimPathOffset), 1.0f); mTrimmedSkPath.reset(); if (start > end) { @@ -255,76 +244,69 @@ void FullPath::applyTrim() { mTrimDirty = false; } -inline int putData(int8_t* outBytes, int startIndex, float value) { - int size = sizeof(float); - memcpy(&outBytes[startIndex], &value, size); - return size; -} - -inline int putData(int8_t* outBytes, int startIndex, int value) { - int size = sizeof(int); - memcpy(&outBytes[startIndex], &value, size); - return size; -} - -struct FullPathProperties { - // TODO: Consider storing full path properties in this struct instead of the fields. - float strokeWidth; - SkColor strokeColor; - float strokeAlpha; - SkColor fillColor; - float fillAlpha; - float trimPathStart; - float trimPathEnd; - float trimPathOffset; - int32_t strokeLineCap; - int32_t strokeLineJoin; - float strokeMiterLimit; -}; - -REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties); +REQUIRE_COMPATIBLE_LAYOUT(FullPath::Properties); static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t"); static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t"); bool FullPath::getProperties(int8_t* outProperties, int length) { - int propertyDataSize = sizeof(FullPathProperties); + int propertyDataSize = sizeof(Properties); if (length != propertyDataSize) { LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", propertyDataSize, length); return false; } - // TODO: consider replacing the property fields with a FullPathProperties struct. - FullPathProperties properties; - properties.strokeWidth = mStrokeWidth; - properties.strokeColor = mStrokeColor; - properties.strokeAlpha = mStrokeAlpha; - properties.fillColor = mFillColor; - properties.fillAlpha = mFillAlpha; - properties.trimPathStart = mTrimPathStart; - properties.trimPathEnd = mTrimPathEnd; - properties.trimPathOffset = mTrimPathOffset; - properties.strokeLineCap = mStrokeLineCap; - properties.strokeLineJoin = mStrokeLineJoin; - properties.strokeMiterLimit = mStrokeMiterLimit; - - memcpy(outProperties, &properties, length); + Properties* out = reinterpret_cast<Properties*>(outProperties); + *out = mProperties; return true; } +void FullPath::setColorPropertyValue(int propertyId, int32_t value) { + Property currentProperty = static_cast<Property>(propertyId); + if (currentProperty == Property::StrokeColor) { + mProperties.strokeColor = value; + } else if (currentProperty == Property::FillColor) { + mProperties.fillColor = value; + } else { + LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property with id: %d", + propertyId); + } +} + +void FullPath::setPropertyValue(int propertyId, float value) { + Property property = static_cast<Property>(propertyId); + switch (property) { + case Property::StrokeWidth: + setStrokeWidth(value); + break; + case Property::StrokeAlpha: + setStrokeAlpha(value); + break; + case Property::FillAlpha: + setFillAlpha(value); + break; + case Property::TrimPathStart: + setTrimPathStart(value); + break; + case Property::TrimPathEnd: + setTrimPathEnd(value); + break; + case Property::TrimPathOffset: + setTrimPathOffset(value); + break; + default: + LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId); + break; + } +} + void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale, const SkMatrix& matrix){ outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); } Group::Group(const Group& group) : Node(group) { - mRotate = group.mRotate; - mPivotX = group.mPivotX; - mPivotY = group.mPivotY; - mScaleX = group.mScaleX; - mScaleY = group.mScaleY; - mTranslateX = group.mTranslateX; - mTranslateY = group.mTranslateY; + mProperties = group.mProperties; } void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, @@ -371,10 +353,11 @@ void Group::getLocalMatrix(SkMatrix* outMatrix) { outMatrix->reset(); // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of // translating to pivot for rotating and scaling, then translating back. - outMatrix->postTranslate(-mPivotX, -mPivotY); - outMatrix->postScale(mScaleX, mScaleY); - outMatrix->postRotate(mRotate, 0, 0); - outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); + outMatrix->postTranslate(-mProperties.pivotX, -mProperties.pivotY); + outMatrix->postScale(mProperties.scaleX, mProperties.scaleY); + outMatrix->postRotate(mProperties.rotate, 0, 0); + outMatrix->postTranslate(mProperties.translateX + mProperties.pivotX, + mProperties.translateY + mProperties.pivotY); } void Group::addChild(Node* child) { @@ -388,38 +371,68 @@ bool Group::getProperties(float* outProperties, int length) { propertyCount, length); return false; } - for (int i = 0; i < propertyCount; i++) { - Property currentProperty = static_cast<Property>(i); - switch (currentProperty) { - case Property::Rotate_Property: - outProperties[i] = mRotate; - break; - case Property::PivotX_Property: - outProperties[i] = mPivotX; - break; - case Property::PivotY_Property: - outProperties[i] = mPivotY; - break; - case Property::ScaleX_Property: - outProperties[i] = mScaleX; - break; - case Property::ScaleY_Property: - outProperties[i] = mScaleY; - break; - case Property::TranslateX_Property: - outProperties[i] = mTranslateX; - break; - case Property::TranslateY_Property: - outProperties[i] = mTranslateY; - break; - default: - LOG_ALWAYS_FATAL("Invalid input index: %d", i); - return false; - } - } + Properties* out = reinterpret_cast<Properties*>(outProperties); + *out = mProperties; return true; } +// TODO: Consider animating the properties as float pointers +float Group::getPropertyValue(int propertyId) const { + Property currentProperty = static_cast<Property>(propertyId); + switch (currentProperty) { + case Property::Rotate: + return mProperties.rotate; + case Property::PivotX: + return mProperties.pivotX; + case Property::PivotY: + return mProperties.pivotY; + case Property::ScaleX: + return mProperties.scaleX; + case Property::ScaleY: + return mProperties.scaleY; + case Property::TranslateX: + return mProperties.translateX; + case Property::TranslateY: + return mProperties.translateY; + default: + LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); + return 0; + } +} + +void Group::setPropertyValue(int propertyId, float value) { + Property currentProperty = static_cast<Property>(propertyId); + switch (currentProperty) { + case Property::Rotate: + mProperties.rotate = value; + break; + case Property::PivotX: + mProperties.pivotX = value; + break; + case Property::PivotY: + mProperties.pivotY = value; + break; + case Property::ScaleX: + mProperties.scaleX = value; + break; + case Property::ScaleY: + mProperties.scaleY = value; + break; + case Property::TranslateX: + mProperties.translateX = value; + break; + case Property::TranslateY: + mProperties.translateY = value; + break; + default: + LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); + } +} + +bool Group::isValidProperty(int propertyId) { + return propertyId >= 0 && propertyId < static_cast<int>(Property::Count); +} + void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, const SkRect& bounds, bool needsMirroring, bool canReuseCache) { // The imageView can scale the canvas in different ways, in order to @@ -445,7 +458,9 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, return; } - int saveCount = outCanvas->save(SaveFlags::MatrixClip); + mPaint.setColorFilter(colorFilter); + + int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag); outCanvas->translate(mBounds.fLeft, mBounds.fTop); // Handle RTL mirroring. @@ -458,43 +473,33 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, // And we use this bound for the destination rect for the drawBitmap, so // we offset to (0, 0); mBounds.offsetTo(0, 0); - createCachedBitmapIfNeeded(scaledWidth, scaledHeight); - if (!mAllowCaching) { - updateCachedBitmap(scaledWidth, scaledHeight); - } else { - if (!canReuseCache || mCacheDirty) { - updateCachedBitmap(scaledWidth, scaledHeight); - } - } - drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds); + + outCanvas->drawVectorDrawable(this); outCanvas->restoreToCount(saveCount); } -void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter, - const SkRect& originalBounds) { +SkPaint* Tree::getPaint() { SkPaint* paint; - if (mRootAlpha == 1.0f && filter == NULL) { + if (mRootAlpha == 1.0f && mPaint.getColorFilter() == NULL) { paint = NULL; } else { mPaint.setFilterQuality(kLow_SkFilterQuality); mPaint.setAlpha(mRootAlpha * 255); - mPaint.setColorFilter(filter); paint = &mPaint; } - outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(), - originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight, - originalBounds.fBottom, paint); + return paint; } -void Tree::updateCachedBitmap(int width, int height) { +const SkBitmap& Tree::getBitmapUpdateIfDirty() { mCachedBitmap.eraseColor(SK_ColorTRANSPARENT); SkCanvas outCanvas(mCachedBitmap); - float scaleX = width / mViewportWidth; - float scaleY = height / mViewportHeight; + float scaleX = (float) mCachedBitmap.width() / mViewportWidth; + float scaleY = (float) mCachedBitmap.height() / mViewportHeight; mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY); mCacheDirty = false; + return mCachedBitmap; } void Tree::createCachedBitmapIfNeeded(int width, int height) { diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 09bdce596a21..f8f1ea62a624 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -18,6 +18,7 @@ #define ANDROID_HWUI_VPATH_H #include "Canvas.h" + #include <SkBitmap.h> #include <SkColor.h> #include <SkCanvas.h> @@ -104,6 +105,21 @@ protected: class ANDROID_API FullPath: public Path { public: + +struct Properties { + float strokeWidth = 0; + SkColor strokeColor = SK_ColorTRANSPARENT; + float strokeAlpha = 1; + SkColor fillColor = SK_ColorTRANSPARENT; + float fillAlpha = 1; + float trimPathStart = 0; + float trimPathEnd = 1; + float trimPathOffset = 0; + int32_t strokeLineCap = SkPaint::Cap::kButt_Cap; + int32_t strokeLineJoin = SkPaint::Join::kMiter_Join; + float strokeMiterLimit = 4; +}; + FullPath(const FullPath& path); // for cloning FullPath(const char* path, size_t strLength) : Path(path, strLength) {} FullPath() : Path() {} @@ -118,55 +134,58 @@ public: float strokeAlpha, SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin); + // TODO: Cleanup: Remove the setter and getters below, and their counterparts in java and JNI float getStrokeWidth() { - return mStrokeWidth; + return mProperties.strokeWidth; } void setStrokeWidth(float strokeWidth) { - mStrokeWidth = strokeWidth; + mProperties.strokeWidth = strokeWidth; } SkColor getStrokeColor() { - return mStrokeColor; + return mProperties.strokeColor; } void setStrokeColor(SkColor strokeColor) { - mStrokeColor = strokeColor; + mProperties.strokeColor = strokeColor; } float getStrokeAlpha() { - return mStrokeAlpha; + return mProperties.strokeAlpha; } void setStrokeAlpha(float strokeAlpha) { - mStrokeAlpha = strokeAlpha; + mProperties.strokeAlpha = strokeAlpha; } SkColor getFillColor() { - return mFillColor; + return mProperties.fillColor; } void setFillColor(SkColor fillColor) { - mFillColor = fillColor; + mProperties.fillColor = fillColor; } float getFillAlpha() { - return mFillAlpha; + return mProperties.fillAlpha; } void setFillAlpha(float fillAlpha) { - mFillAlpha = fillAlpha; + mProperties.fillAlpha = fillAlpha; } float getTrimPathStart() { - return mTrimPathStart; + return mProperties.trimPathStart; } void setTrimPathStart(float trimPathStart) { - VD_SET_PROP_WITH_FLAG(mTrimPathStart, trimPathStart, mTrimDirty); + VD_SET_PROP_WITH_FLAG(mProperties.trimPathStart, trimPathStart, mTrimDirty); } float getTrimPathEnd() { - return mTrimPathEnd; + return mProperties.trimPathEnd; } void setTrimPathEnd(float trimPathEnd) { - VD_SET_PROP_WITH_FLAG(mTrimPathEnd, trimPathEnd, mTrimDirty); + VD_SET_PROP_WITH_FLAG(mProperties.trimPathEnd, trimPathEnd, mTrimDirty); } float getTrimPathOffset() { - return mTrimPathOffset; + return mProperties.trimPathOffset; } void setTrimPathOffset(float trimPathOffset) { - VD_SET_PROP_WITH_FLAG(mTrimPathOffset, trimPathOffset, mTrimDirty); + VD_SET_PROP_WITH_FLAG(mProperties.trimPathOffset, trimPathOffset, mTrimDirty); } bool getProperties(int8_t* outProperties, int length); + void setColorPropertyValue(int propertyId, int32_t value); + void setPropertyValue(int propertyId, float value); void setFillGradient(SkShader* fillGradient) { SkRefCnt_SafeAssign(mFillGradient, fillGradient); @@ -182,24 +201,28 @@ protected: float strokeScale, const SkMatrix& matrix) override; private: + enum class Property { + StrokeWidth = 0, + StrokeColor, + StrokeAlpha, + FillColor, + FillAlpha, + TrimPathStart, + TrimPathEnd, + TrimPathOffset, + StrokeLineCap, + StrokeLineJoin, + StrokeMiterLimit, + Count, + }; // Applies trimming to the specified path. void applyTrim(); - float mStrokeWidth = 0; - SkColor mStrokeColor = SK_ColorTRANSPARENT; - float mStrokeAlpha = 1; - SkColor mFillColor = SK_ColorTRANSPARENT; - SkShader* mStrokeGradient = nullptr; - SkShader* mFillGradient = nullptr; - float mFillAlpha = 1; - float mTrimPathStart = 0; - float mTrimPathEnd = 1; - float mTrimPathOffset = 0; + Properties mProperties; bool mTrimDirty = true; - SkPaint::Cap mStrokeLineCap = SkPaint::Cap::kButt_Cap; - SkPaint::Join mStrokeLineJoin = SkPaint::Join::kMiter_Join; - float mStrokeMiterLimit = 4; SkPath mTrimmedSkPath; SkPaint mPaint; + SkShader* mStrokeGradient = nullptr; + SkShader* mFillGradient = nullptr; }; class ANDROID_API ClipPath: public Path { @@ -216,49 +239,58 @@ protected: class ANDROID_API Group: public Node { public: + struct Properties { + float rotate = 0; + float pivotX = 0; + float pivotY = 0; + float scaleX = 1; + float scaleY = 1; + float translateX = 0; + float translateY = 0; + }; Group(const Group& group); Group() {} float getRotation() { - return mRotate; + return mProperties.rotate; } void setRotation(float rotation) { - mRotate = rotation; + mProperties.rotate = rotation; } float getPivotX() { - return mPivotX; + return mProperties.pivotX; } void setPivotX(float pivotX) { - mPivotX = pivotX; + mProperties.pivotX = pivotX; } float getPivotY() { - return mPivotY; + return mProperties.pivotY; } void setPivotY(float pivotY) { - mPivotY = pivotY; + mProperties.pivotY = pivotY; } float getScaleX() { - return mScaleX; + return mProperties.scaleX; } void setScaleX(float scaleX) { - mScaleX = scaleX; + mProperties.scaleX = scaleX; } float getScaleY() { - return mScaleY; + return mProperties.scaleY; } void setScaleY(float scaleY) { - mScaleY = scaleY; + mProperties.scaleY = scaleY; } float getTranslateX() { - return mTranslateX; + return mProperties.translateX; } void setTranslateX(float translateX) { - mTranslateX = translateX; + mProperties.translateX = translateX; } float getTranslateY() { - return mTranslateY; + return mProperties.translateY; } void setTranslateY(float translateY) { - mTranslateY = translateY; + mProperties.translateY = translateY; } virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, float scaleY) override; @@ -268,38 +300,33 @@ public: void addChild(Node* child); void dump() override; bool getProperties(float* outProperties, int length); + float getPropertyValue(int propertyId) const; + void setPropertyValue(int propertyId, float value); + static bool isValidProperty(int propertyId); private: enum class Property { - Rotate_Property = 0, - PivotX_Property, - PivotY_Property, - ScaleX_Property, - ScaleY_Property, - TranslateX_Property, - TranslateY_Property, + Rotate = 0, + PivotX, + PivotY, + ScaleX, + ScaleY, + TranslateX, + TranslateY, // Count of the properties, must be at the end. Count, }; - float mRotate = 0; - float mPivotX = 0; - float mPivotY = 0; - float mScaleX = 1; - float mScaleY = 1; - float mTranslateX = 0; - float mTranslateY = 0; std::vector<Node*> mChildren; + Properties mProperties; }; -class ANDROID_API Tree { +class ANDROID_API Tree : public VirtualLightRefBase { public: Tree(Group* rootNode) : mRootNode(rootNode) {} void draw(Canvas* outCanvas, SkColorFilter* colorFilter, const SkRect& bounds, bool needsMirroring, bool canReuseCache); - void drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter, - const SkRect& originalBounds); - void updateCachedBitmap(int width, int height); + const SkBitmap& getBitmapUpdateIfDirty(); void createCachedBitmapIfNeeded(int width, int height); bool canReuseBitmap(int width, int height); void setAllowCaching(bool allowCaching) { @@ -316,6 +343,10 @@ public: mViewportWidth = viewportWidth; mViewportHeight = viewportHeight; } + SkPaint* getPaint(); + const SkRect& getBounds() const { + return mBounds; + } private: // Cap the bitmap size, such that it won't hurt the performance too much diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 93e6dacdd98c..80b3ffc23e31 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -115,7 +115,7 @@ public final class MediaBrowser { * @param callback The connection callback. * @param rootHints An optional bundle of service-specific arguments to send * to the media browse service when connecting and retrieving the root id - * for browsing, or null if none. The contents of this bundle may affect + * for browsing, or null if none. The contents of this bundle may affect * the information returned when browsing. */ public MediaBrowser(Context context, ComponentName serviceComponent, @@ -178,9 +178,9 @@ public final class MediaBrowser { } if (!bound) { - // Tell them that it didn't work. We are already on the main thread, - // but we don't want to do callbacks inside of connect(). So post it, - // and then check that we are on the same ServiceConnection. We know + // Tell them that it didn't work. We are already on the main thread, + // but we don't want to do callbacks inside of connect(). So post it, + // and then check that we are on the same ServiceConnection. We know // we won't also get an onServiceConnected or onServiceDisconnected, // so we won't be doing double callbacks. mHandler.post(new Runnable() { @@ -207,13 +207,13 @@ public final class MediaBrowser { */ public void disconnect() { // It's ok to call this any state, because allowing this lets apps not have - // to check isConnected() unnecessarily. They won't appreciate the extra - // assertions for this. We do everything we can here to go back to a sane state. + // to check isConnected() unnecessarily. They won't appreciate the extra + // assertions for this. We do everything we can here to go back to a sane state. if (mServiceCallbacks != null) { try { mServiceBinder.disconnect(mServiceCallbacks); } catch (RemoteException ex) { - // We are disconnecting anyway. Log, just for posterity but it's not + // We are disconnecting anyway. Log, just for posterity but it's not // a big problem. Log.w(TAG, "RemoteException during connect for " + mServiceComponent); } @@ -227,12 +227,12 @@ public final class MediaBrowser { } /** - * Null out the variables and unbind from the service. This doesn't include + * Null out the variables and unbind from the service. This doesn't include * calling disconnect on the service, because we only try to do that in the * clean shutdown cases. * <p> * Everywhere that calls this EXCEPT for disconnect() should follow it with - * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback + * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback * for a clean shutdown, but everywhere else is a dirty shutdown and should * notify the app. */ @@ -455,8 +455,8 @@ public final class MediaBrowser { private void subscribeInternal(String parentId, Bundle options, SubscriptionCallback callback) { // Check arguments. - if (parentId == null) { - throw new IllegalArgumentException("parentId is null"); + if (TextUtils.isEmpty(parentId)) { + throw new IllegalArgumentException("parentId is empty."); } if (callback == null) { throw new IllegalArgumentException("callback is null"); @@ -659,7 +659,7 @@ public final class MediaBrowser { } /** - * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not. + * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not. */ private boolean isCurrent(IMediaBrowserServiceCallbacks callback, String funcName) { if (mServiceCallbacks != callback) { @@ -955,8 +955,8 @@ public final class MediaBrowser { mServiceCallbacks); } catch (RemoteException ex) { // Connect failed, which isn't good. But the auto-reconnect on the service - // will take over and we will come back. We will also get the - // onServiceDisconnected, which has all the cleanup code. So let that do + // will take over and we will come back. We will also get the + // onServiceDisconnected, which has all the cleanup code. So let that do // it. Log.w(TAG, "RemoteException during connect for " + mServiceComponent); if (DBG) { @@ -1005,7 +1005,7 @@ public final class MediaBrowser { } /** - * Return true if this is the current ServiceConnection. Also logs if it's not. + * Return true if this is the current ServiceConnection. Also logs if it's not. */ private boolean isCurrent(String funcName) { if (mServiceConnection != this) { @@ -1031,7 +1031,7 @@ public final class MediaBrowser { } /** - * The other side has acknowledged our connection. The parameters to this function + * The other side has acknowledged our connection. The parameters to this function * are the initial data as requested. */ @Override @@ -1044,7 +1044,7 @@ public final class MediaBrowser { } /** - * The other side does not like us. Tell the app via onConnectionFailed. + * The other side does not like us. Tell the app via onConnectionFailed. */ @Override public void onConnectFailed() { diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java index 4f198aca96ab..b06e598a63d1 100644 --- a/media/java/android/media/browse/MediaBrowserUtils.java +++ b/media/java/android/media/browse/MediaBrowserUtils.java @@ -40,10 +40,10 @@ public class MediaBrowserUtils { } public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) { - int page1 = options1.getInt(MediaBrowser.EXTRA_PAGE, -1); - int page2 = options2.getInt(MediaBrowser.EXTRA_PAGE, -1); - int pageSize1 = options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1); - int pageSize2 = options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1); + int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1); + int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1); + int pageSize1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1); + int pageSize2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1); int startIndex1, startIndex2, endIndex1, endIndex2; if (page1 == -1 || pageSize1 == -1) { diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 299b77071c55..0393c943784d 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -16,6 +16,7 @@ package android.service.media; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -41,6 +42,8 @@ import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -49,7 +52,7 @@ import java.util.List; * Base class for media browse services. * <p> * Media browse services enable applications to browse media content provided by an application - * and ask the application to start playing it. They may also be used to control content that + * and ask the application to start playing it. They may also be used to control content that * is already playing by way of a {@link MediaSession}. * </p> * @@ -86,6 +89,11 @@ public abstract class MediaBrowserService extends Service { private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED }) + private @interface ResultFlags { } + private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>(); private final Handler mHandler = new Handler(); private ServiceBinder mBinder; @@ -106,10 +114,10 @@ public abstract class MediaBrowserService extends Service { * Completion handler for asynchronous callback methods in {@link MediaBrowserService}. * <p> * Each of the methods that takes one of these to send the result must call - * {@link #sendResult} to respond to the caller with the given results. If those + * {@link #sendResult} to respond to the caller with the given results. If those * functions return without calling {@link #sendResult}, they must instead call * {@link #detach} before returning, and then may call {@link #sendResult} when - * they are done. If more than one of those methods is called, an exception will + * they are done. If more than one of those methods is called, an exception will * be thrown. * * @see MediaBrowserService#onLoadChildren @@ -119,7 +127,7 @@ public abstract class MediaBrowserService extends Service { private Object mDebug; private boolean mDetachCalled; private boolean mSendResultCalled; - private int mFlag; + private int mFlags; Result(Object debug) { mDebug = debug; @@ -133,7 +141,7 @@ public abstract class MediaBrowserService extends Service { throw new IllegalStateException("sendResult() called twice for: " + mDebug); } mSendResultCalled = true; - onResultSent(result, mFlag); + onResultSent(result, mFlags); } /** @@ -156,15 +164,15 @@ public abstract class MediaBrowserService extends Service { return mDetachCalled || mSendResultCalled; } - void setFlag(int flag) { - mFlag = flag; + void setFlags(@ResultFlags int flags) { + mFlags = flags; } /** * Called when the result is sent, after assertions about not being called twice * have happened. */ - void onResultSent(T result, int flag) { + void onResultSent(T result, @ResultFlags int flags) { } } @@ -184,7 +192,7 @@ public abstract class MediaBrowserService extends Service { public void run() { final IBinder b = callbacks.asBinder(); - // Clear out the old subscriptions. We are getting new ones. + // Clear out the old subscriptions. We are getting new ones. mConnections.remove(b); final ConnectionRecord connection = new ConnectionRecord(); @@ -228,7 +236,7 @@ public abstract class MediaBrowserService extends Service { public void run() { final IBinder b = callbacks.asBinder(); - // Clear out the old subscriptions. We are getting new ones. + // Clear out the old subscriptions. We are getting new ones. final ConnectionRecord old = mConnections.remove(b); if (old != null) { // TODO @@ -388,7 +396,7 @@ public abstract class MediaBrowserService extends Service { // To support backward compatibility, when the implementation of MediaBrowserService doesn't // override onLoadChildren() with options, onLoadChildren() without options will be used // instead, and the options will be applied in the implementation of result.onResultSent(). - result.setFlag(RESULT_FLAG_OPTION_NOT_HANDLED); + result.setFlags(RESULT_FLAG_OPTION_NOT_HANDLED); onLoadChildren(parentId, result); } @@ -574,7 +582,7 @@ public abstract class MediaBrowserService extends Service { final Result<List<MediaBrowser.MediaItem>> result = new Result<List<MediaBrowser.MediaItem>>(parentId) { @Override - void onResultSent(List<MediaBrowser.MediaItem> list, int flag) { + void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) { if (mConnections.get(connection.callbacks.asBinder()) != connection) { if (DBG) { Log.d(TAG, "Not sending onLoadChildren result for connection that has" @@ -639,7 +647,7 @@ public abstract class MediaBrowserService extends Service { final Result<MediaBrowser.MediaItem> result = new Result<MediaBrowser.MediaItem>(itemId) { @Override - void onResultSent(MediaBrowser.MediaItem item, int flag) { + void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { Bundle bundle = new Bundle(); bundle.putParcelable(KEY_MEDIA_ITEM, item); receiver.send(0, bundle); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java index 74da2c9b1955..e718742d5a67 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java @@ -90,7 +90,7 @@ public class Camera2SurfaceViewTestCase extends private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000; // Instrumentation arguments - protected static final String ARG_KEY_REPEAT = "repeat"; + protected static final String ARG_KEY_ITERATIONS = "iterations"; protected static final String ARG_KEY_WAIT_INTERVAL_MS = "waitIntervalMs"; protected static final String ARG_KEY_RESULT_TO_FILE = "resultToFile"; @@ -126,8 +126,8 @@ public class Camera2SurfaceViewTestCase extends protected WindowManager mWindowManager; - // Repeat tests a given times. Default to 1. - protected int mRepeat = 1; + // Set the number of iterations to run stress testing. Default to 1. + protected int mIterations = 1; // The interval between test iterations used for stress test. protected long mTestWaitIntervalMs = 1 * 1000; // 1 sec protected boolean mWriteToFile = true; @@ -165,10 +165,10 @@ public class Camera2SurfaceViewTestCase extends mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - mRepeat = getArgumentsAsNumber(ARG_KEY_REPEAT, 1).intValue(); + mIterations = getArgumentsAsNumber(ARG_KEY_ITERATIONS, 1).intValue(); mTestWaitIntervalMs = getArgumentsAsNumber(ARG_KEY_WAIT_INTERVAL_MS, 1000).longValue(); mWriteToFile = getArgumentsAsBoolean(ARG_KEY_RESULT_TO_FILE, true); - Log.i(TAG, "Argument: repeat count=" + mRepeat); + Log.i(TAG, "Argument: iteration count=" + mIterations); Log.i(TAG, "Argument: interval (ms)=" + mTestWaitIntervalMs); Log.i(TAG, "Argument: result to file=" + (mWriteToFile ? "true" : "false")); mResultPrinter = new CameraTestResultPrinter(getInstrumentation(), mWriteToFile); @@ -790,8 +790,8 @@ public class Camera2SurfaceViewTestCase extends return defaultValue; } - protected int getRepeatCount() { - return mRepeat; + protected int getIterationCount() { + return mIterations; } protected long getTestWaitIntervalMs() { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java index b1529756c8fe..ebfd92eec073 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java @@ -24,7 +24,6 @@ import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.util.Log; -import android.util.Rational; import android.util.Size; import java.util.Arrays; @@ -47,7 +46,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNot * * adb shell am instrument \ * -e class com.android.mediaframeworktest.stress.Camera2CaptureRequestTest#testAeModeAndLock \ - * -e repeat 10 \ + * -e iterations 10 \ * -e waitIntervalMs 1000 \ * -e resultToFile false \ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner @@ -100,16 +99,16 @@ public class Camera2CaptureRequestTest extends Camera2SurfaceViewTestCase { updatePreviewSurface(maxPreviewSz); // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("AE mode and lock: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("AE mode and lock: %d/%d", iteration + 1, + getIterationCount())); // Test aeMode and lock int[] aeModes = mStaticInfo.getAeAvailableModesChecked(); for (int mode : aeModes) { aeModeAndLockTestByMode(mode); } - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, mCameraIds[i]); Thread.sleep(getTestWaitIntervalMs()); } } finally { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java index e7c91cf2bc98..a9b6bfd783f6 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java @@ -67,7 +67,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNot * * adb shell am instrument \ * -e class com.android.mediaframeworktest.stress.Camera2RecordingTest#testBasicRecording \ - * -e repeat 10 \ + * -e iterations 10 \ * -e waitIntervalMs 1000 \ * -e resultToFile false \ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner @@ -142,11 +142,12 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase { initSupportedVideoSize(mCameraIds[i]); // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Recording video: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Recording video: %d/%d", iteration + 1, + getIterationCount())); basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, + mCameraIds[i]); Thread.sleep(getTestWaitIntervalMs()); } } finally { @@ -206,9 +207,9 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase { } // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Constrained high speed recording: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Constrained high speed recording: %d/%d", + iteration + 1, getIterationCount())); StreamConfigurationMap config = mStaticInfo.getValueFromKeyNonNull( @@ -257,7 +258,7 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase { validateRecording(size, durationMs); } - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } } @@ -481,11 +482,11 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase { initSupportedVideoSize(id); // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Video snapshot: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Video snapshot: %d/%d", iteration + 1, + getIterationCount())); videoSnapshotTestByCamera(burstTest); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } } finally { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java index 2dac37128ae9..8f9489790bdb 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java @@ -58,7 +58,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.verifyJpegK * adb shell am instrument \ * -e class \ * com.android.mediaframeworktest.stress.Camera2StillCaptureTest#Camera2ReprocessCaptureTest \ - * -e repeat 1 \ + * -e iterations 1 \ * -e waitIntervalMs 1000 \ * -e resultToFile false \ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner @@ -110,12 +110,12 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase { } // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Reprocessing YUV to JPEG: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Reprocessing YUV to JPEG: %d/%d", iteration + 1, + getIterationCount())); // YUV_420_888 -> JPEG must be supported. testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } } @@ -131,12 +131,12 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase { } // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Reprocessing OPAQUE to JPEG: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Reprocessing OPAQUE to JPEG: %d/%d", iteration + 1, + getIterationCount())); // OPAQUE -> JPEG must be supported. testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } @@ -157,12 +157,12 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase { openDevice(id); // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { Log.v(TAG, String.format("Reprocessing size format with preview: %d/%d", - repeat + 1, getRepeatCount())); + iteration + 1, getIterationCount())); testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), CaptureTestCase.SINGLE_SHOT); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } } finally { @@ -185,16 +185,16 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase { openDevice(id); // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { Log.v(TAG, String.format("Reprocessing mixed burst with or without preview: " - + "%d/%d", repeat + 1, getRepeatCount())); + + "%d/%d", iteration + 1, getIterationCount())); // no preview testReprocessingAllCombinations(id, /*previewSize*/null, CaptureTestCase.MIXED_BURST); // with preview testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), CaptureTestCase.MIXED_BURST); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } } finally { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java index 16dfb2bbd3e7..812543b3fe41 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java @@ -59,7 +59,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageRe * * adb shell am instrument \ * -e class com.android.mediaframeworktest.stress.Camera2StillCaptureTest#testTakePicture \ - * -e repeat 200 \ + * -e iterations 200 \ * -e waitIntervalMs 1000 \ * -e resultToFile false \ * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner @@ -108,12 +108,12 @@ public class Camera2StillCaptureTest extends Camera2SurfaceViewTestCase { } // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Taking pictures: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Taking pictures: %d/%d", iteration + 1, + getIterationCount())); takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null, /*afRegions*/null); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, id); Thread.sleep(getTestWaitIntervalMs()); } } finally { @@ -144,11 +144,12 @@ public class Camera2StillCaptureTest extends Camera2SurfaceViewTestCase { } // Test iteration starts... - for (int repeat = 0; repeat < getRepeatCount(); ++repeat) { - Log.v(TAG, String.format("Taking full RAW pictures: %d/%d", repeat + 1, - getRepeatCount())); + for (int iteration = 0; iteration < getIterationCount(); ++iteration) { + Log.v(TAG, String.format("Taking full RAW pictures: %d/%d", iteration + 1, + getIterationCount())); fullRawCaptureTestByCamera(); - getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]); + getResultPrinter().printStatus(getIterationCount(), iteration + 1, + mCameraIds[i]); Thread.sleep(getTestWaitIntervalMs()); } } finally { diff --git a/packages/DocumentsUI/lint.xml b/packages/DocumentsUI/lint.xml new file mode 100644 index 000000000000..70d1ddff4074 --- /dev/null +++ b/packages/DocumentsUI/lint.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> + <!-- min-sdk doesn't apply to platform apps --> + <issue id="UsesMinSdkAttributes" severity="ignore" /> + + <!-- Protected permissions don't apply to system apps --> + <issue id="ProtectedPermissions" severity="ignore" /> +</lint> diff --git a/packages/DocumentsUI/res/drawable/ic_sd_storage.xml b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml new file mode 100644 index 000000000000..b0f3cc395bcd --- /dev/null +++ b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml @@ -0,0 +1,24 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M18 2h-8L4.02 8 4 20c0 1.1.9 2 2 2h12c1.1 0 2,-.9 2,-2V4c0,-1.1,-.9,-2,-2,-2zm-6 6h-2V4h2v4zm3 0h-2V4h2v4zm3 0h-2V4h2v4z"/> +</vector> diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml index a3cfde825f47..b791ef12b6f3 100644 --- a/packages/DocumentsUI/res/menu/activity.xml +++ b/packages/DocumentsUI/res/menu/activity.xml @@ -29,7 +29,8 @@ android:icon="@drawable/ic_menu_search" android:showAsAction="always" android:actionViewClass="android.widget.SearchView" - android:imeOptions="actionSearch" /> + android:imeOptions="actionSearch" + android:visible="false" /> <item android:id="@+id/menu_sort" android:title="@string/menu_sort" @@ -51,12 +52,13 @@ android:id="@+id/menu_grid" android:title="@string/menu_grid" android:icon="@drawable/ic_menu_view_grid" - android:showAsAction="never" /> + android:showAsAction="always" /> <item android:id="@+id/menu_list" android:title="@string/menu_list" android:icon="@drawable/ic_menu_view_list" - android:showAsAction="never" /> + android:showAsAction="always" /> + <item android:id="@+id/menu_new_window" android:title="@string/menu_new_window" @@ -88,5 +90,6 @@ <item android:id="@+id/menu_settings" android:title="@string/menu_settings" - android:showAsAction="never" /> + android:showAsAction="never" + android:visible="false" /> </menu> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index afe93369ae2b..3c49f167534e 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -39,8 +39,8 @@ <string name="menu_sort">Sort by</string> <!-- Menu item that enters a mode to search for documents [CHAR LIMIT=24] --> <string name="menu_search">Search</string> - <!-- Menu item that enters activity to change settings [CHAR LIMIT=24] --> - <string name="menu_settings">Settings</string> + <!-- Menu item that enters activity to change settings for current root [CHAR LIMIT=24] --> + <string name="menu_settings">Storage settings</string> <!-- Menu item title that opens the selected documents [CHAR LIMIT=24] --> <string name="menu_open">Open</string> diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 1474aa6b7690..0fed6410b2cd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -38,6 +38,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Root; +import android.support.annotation.CallSuper; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.util.Log; @@ -138,10 +139,12 @@ public abstract class BaseActivity extends Activity implements SearchManagerList } @Override + @CallSuper public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - final RootInfo root = getCurrentRoot(); + mSearchManager.showMenu(canSearchRoot()); + final boolean inRecents = getCurrentDirectory() == null; final MenuItem sort = menu.findItem(R.id.menu_sort); @@ -150,26 +153,17 @@ public abstract class BaseActivity extends Activity implements SearchManagerList final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem advanced = menu.findItem(R.id.menu_advanced); final MenuItem fileSize = menu.findItem(R.id.menu_file_size); - final MenuItem settings = menu.findItem(R.id.menu_settings); - final MenuItem search = menu.findItem(R.id.menu_search); - - // I'm thinkin' this isn't necesary here. If it is...'cuz of a bug.... - // then uncomment the linke and let's get a proper bug reference here. - // mSearchManager.update(root); - // Search uses backend ranking; no sorting + // Search uses backend ranking; no sorting, recents doesn't support sort. sort.setVisible(!inRecents && !mSearchManager.isSearching()); + sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible + fileSize.setVisible(!mState.forceSize); // grid/list is effectively a toggle. grid.setVisible(mState.derivedMode != State.MODE_GRID); list.setVisible(mState.derivedMode != State.MODE_LIST); - sortSize.setVisible(mState.showSize); // Only sort by size when visible - fileSize.setVisible(!mState.forceSize); advanced.setVisible(!mState.forceAdvanced); - settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0); - search.setVisible(canSearchRoot()); - advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this) ? R.string.menu_advanced_hide : R.string.menu_advanced_show); fileSize.setTitle(LocalPreferences.getDisplayFileSize(this) @@ -273,8 +267,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList return true; case R.id.menu_paste_from_clipboard: - DirectoryFragment.get(getFragmentManager()) - .pasteFromClipboard(); + DirectoryFragment dir = getDirectoryFragment(); + if (dir != null) { + dir.pasteFromClipboard(); + } return true; case R.id.menu_advanced: @@ -297,6 +293,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList } } + final @Nullable DirectoryFragment getDirectoryFragment() { + return DirectoryFragment.get(getFragmentManager()); + } + void showCreateDirectoryDialog() { CreateDirectoryFragment.show(getFragmentManager()); } @@ -425,7 +425,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList void setDisplayFileSize(boolean display) { LocalPreferences.setDisplayFileSize(this, display); mState.showSize = display; - DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged(); + DirectoryFragment dir = getDirectoryFragment(); + if (dir != null) { + dir.onDisplayStateChanged(); + } invalidateOptionsMenu(); } @@ -434,7 +437,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList */ void setUserSortOrder(int sortOrder) { mState.userSortOrder = sortOrder; - DirectoryFragment.get(getFragmentManager()).onSortOrderChanged(); + DirectoryFragment dir = getDirectoryFragment(); + if (dir != null) { + dir.onSortOrderChanged(); + }; } /** @@ -449,7 +455,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList // in onOptionsItemSelected, and not do the full invalidation // But! That's a larger refactoring we'll save for another day. invalidateOptionsMenu(); - DirectoryFragment.get(getFragmentManager()).onViewModeChanged(); + DirectoryFragment dir = getDirectoryFragment(); + if (dir != null) { + dir.onViewModeChanged(); + }; } public void setPending(boolean pending) { @@ -498,7 +507,8 @@ public abstract class BaseActivity extends Activity implements SearchManagerList return; } - if (DirectoryFragment.get(getFragmentManager()).onBackPressed()) { + DirectoryFragment dir = getDirectoryFragment(); + if (dir != null && dir.onBackPressed()) { return; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index c3395aeb7ba2..b933d0a8744c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -293,19 +293,24 @@ public class DocumentsActivity extends BaseActivity { final DocumentInfo cwd = getCurrentDirectory(); + boolean picking = mState.action == ACTION_CREATE + || mState.action == ACTION_OPEN_TREE + || mState.action == ACTION_PICK_COPY_DESTINATION; + + if (picking) { + // May already be hidden because the root + // doesn't support search. + mSearchManager.showMenu(false); + } + final MenuItem createDir = menu.findItem(R.id.menu_create_dir); final MenuItem grid = menu.findItem(R.id.menu_grid); final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem fileSize = menu.findItem(R.id.menu_file_size); - final MenuItem settings = menu.findItem(R.id.menu_settings); boolean recents = cwd == null; - boolean picking = mState.action == ACTION_CREATE - || mState.action == ACTION_OPEN_TREE - || mState.action == ACTION_PICK_COPY_DESTINATION; createDir.setVisible(picking && !recents && cwd.isCreateSupported()); - mSearchManager.showMenu(!picking); // No display options in recent directories if (picking && recents) { @@ -314,7 +319,6 @@ public class DocumentsActivity extends BaseActivity { } fileSize.setVisible(fileSize.isVisible() && !picking); - settings.setVisible(false); if (mState.action == ACTION_CREATE) { final FragmentManager fm = getFragmentManager(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java index 3302da97bcae..b6ded6c3fbd7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java @@ -150,14 +150,12 @@ public class DownloadsActivity extends BaseActivity { final MenuItem newWindow = menu.findItem(R.id.menu_new_window); final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard); final MenuItem fileSize = menu.findItem(R.id.menu_file_size); - final MenuItem search = menu.findItem(R.id.menu_search); advanced.setVisible(false); createDir.setVisible(false); pasteFromCb.setEnabled(false); newWindow.setEnabled(false); fileSize.setVisible(false); - search.setVisible(false); Menus.disableHiddenItems(menu); return true; diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java index 10a78b9f8b51..99b425e9fac2 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Events.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java @@ -78,6 +78,27 @@ public final class Events { } /** + * Whether or not the given keyCode represents a navigation keystroke (e.g. up, down, home). + * + * @param keyCode + * @return + */ + public static boolean isNavigationKeyCode(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_MOVE_HOME: + case KeyEvent.KEYCODE_MOVE_END: + return true; + default: + return false; + } + } + + + /** * Returns true if the "SHIFT" bit is set. */ public static boolean hasShiftBit(int metaState) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java index b490c002dc6c..f0df3a2ce823 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java @@ -246,15 +246,20 @@ public class FilesActivity extends BaseActivity { public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); + final RootInfo root = getCurrentRoot(); + final MenuItem createDir = menu.findItem(R.id.menu_create_dir); - final MenuItem newWindow = menu.findItem(R.id.menu_new_window); final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard); + final MenuItem settings = menu.findItem(R.id.menu_settings); - createDir.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); createDir.setVisible(true); createDir.setEnabled(canCreateDirectory()); - pasteFromCb.setEnabled(mClipper.hasItemsToPaste()); + settings.setVisible(root.hasSettings()); + + // TODO: For some reason settings menu item is not + // honoring the "showAsAction=never" setting in activity.xml. + settings.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); Menus.disableHiddenItems(menu, pasteFromCb); return true; @@ -271,9 +276,10 @@ public class FilesActivity extends BaseActivity { createNewWindow(); return true; case R.id.menu_paste_from_clipboard: - DirectoryFragment dir = DirectoryFragment.get(getFragmentManager()); - dir = DirectoryFragment.get(getFragmentManager()); - dir.pasteFromClipboard(); + DirectoryFragment dir = getDirectoryFragment(); + if (dir != null) { + dir.pasteFromClipboard(); + } return true; } @@ -376,20 +382,26 @@ public class FilesActivity extends BaseActivity { @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { DirectoryFragment dir; + // TODO: All key events should be statically bound using alphabeticShortcut. + // But not working. switch (keyCode) { case KeyEvent.KEYCODE_A: - dir = DirectoryFragment.get(getFragmentManager()); - dir.selectAllFiles(); + dir = getDirectoryFragment(); + if (dir != null) { + dir.selectAllFiles(); + } return true; case KeyEvent.KEYCODE_C: - // TODO: Should be statically bound using alphabeticShortcut. See b/21330356. - dir = DirectoryFragment.get(getFragmentManager()); - dir.copySelectedToClipboard(); + dir = getDirectoryFragment(); + if (dir != null) { + dir.copySelectedToClipboard(); + } return true; case KeyEvent.KEYCODE_V: - // TODO: Should be statically bound using alphabeticShortcut. See b/21330356. - dir = DirectoryFragment.get(getFragmentManager()); - dir.pasteFromClipboard(); + dir = getDirectoryFragment(); + if (dir != null) { + dir.pasteFromClipboard(); + } return true; default: return super.onKeyShortcut(keyCode, event); diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java index bae334b3759f..62d1c5a2de79 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java @@ -354,7 +354,7 @@ public final class Metrics { * a single ROOT_OTHER bucket. */ private static @Root int sanitizeRoot(Uri uri) { - if (LauncherActivity.isLaunchUri(uri)) { + if (uri == null || LauncherActivity.isLaunchUri(uri)) { return ROOT_NONE; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index c2aeb86a1f07..d3a35d95fe32 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -302,8 +302,10 @@ public class RootsFragment extends Fragment { for (final RootInfo root : roots) { final RootItem item = new RootItem(root); if (root.isLibrary()) { + if (DEBUG) Log.d(TAG, "Adding " + root + " as library."); libraries.add(item); } else { + if (DEBUG) Log.d(TAG, "Adding " + root + " as non-library."); others.add(item); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 2aabc991431c..eef77600d700 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -66,6 +66,7 @@ import android.util.TypedValue; import android.view.ActionMode; import android.view.DragEvent; import android.view.GestureDetector; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -99,7 +100,6 @@ import com.android.documentsui.model.DocumentStack; import com.android.documentsui.model.RootInfo; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperations; - import com.google.common.collect.Lists; import java.util.ArrayList; @@ -529,6 +529,9 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi final Cursor cursor = mModel.getItem(modelId); checkNotNull(cursor, "Cursor cannot be null."); + // TODO: Should this be happening in onSelectionChanged? Technically this callback is + // triggered on "silent" selection updates (i.e. we might be reacting to unfinalized + // selection changes here) final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS); if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) { mNoDeleteCount += selected ? 1 : -1; @@ -827,7 +830,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi @Override public void initDocumentHolder(DocumentHolder holder) { holder.addEventListener(mItemEventListener); - holder.addOnKeyListener(mSelectionManager); } @Override @@ -1230,7 +1232,12 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi private class ItemEventListener implements DocumentHolder.EventListener { @Override public boolean onActivate(DocumentHolder doc) { - handleViewItem(doc.modelId); + // Toggle selection if we're in selection mode, othewise, view item. + if (mSelectionManager.hasSelection()) { + mSelectionManager.toggleSelection(doc.modelId); + } else { + handleViewItem(doc.modelId); + } return true; } @@ -1240,6 +1247,128 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition()); return true; } + + @Override + public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) { + // Only handle key-down events. This is simpler, consistent with most other UIs, and + // enables the handling of repeated key events from holding down a key. + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + boolean handled = false; + if (Events.isNavigationKeyCode(keyCode)) { + // Find the target item and focus it. + int endPos = findTargetPosition(doc.itemView, keyCode); + + if (endPos != RecyclerView.NO_POSITION) { + focusItem(endPos); + + // Handle any necessary adjustments to selection. + boolean extendSelection = event.isShiftPressed(); + if (extendSelection) { + int startPos = doc.getAdapterPosition(); + mSelectionManager.selectRange(startPos, endPos); + } + handled = true; + } + } else { + // Handle enter key events + if (keyCode == KeyEvent.KEYCODE_ENTER) { + handled = onActivate(doc); + } + } + + return handled; + } + + /** + * Finds the destination position where the focus should land for a given navigation event. + * + * @param view The view that received the event. + * @param keyCode The key code for the event. + * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION. + */ + private int findTargetPosition(View view, int keyCode) { + if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) { + return 0; + } + + if (keyCode == KeyEvent.KEYCODE_MOVE_END) { + return mAdapter.getItemCount() - 1; + } + + // Find a navigation target based on the arrow key that the user pressed. + int searchDir = -1; + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + searchDir = View.FOCUS_UP; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + searchDir = View.FOCUS_DOWN; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + searchDir = View.FOCUS_LEFT; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + searchDir = View.FOCUS_RIGHT; + break; + } + + if (searchDir != -1) { + View targetView = view.focusSearch(searchDir); + // TargetView can be null, for example, if the user pressed <down> at the bottom + // of the list. + if (targetView != null) { + // Ignore navigation targets that aren't items in the RecyclerView. + if (targetView.getParent() == mRecView) { + return mRecView.getChildAdapterPosition(targetView); + } + } + } + + return RecyclerView.NO_POSITION; + } + + /** + * Requests focus for the item in the given adapter position, scrolling the RecyclerView if + * necessary. + * + * @param pos + */ + public void focusItem(final int pos) { + // If the item is already in view, focus it; otherwise, scroll to it and focus it. + RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos); + if (vh != null) { + vh.itemView.requestFocus(); + } else { + mRecView.smoothScrollToPosition(pos); + // Set a one-time listener to request focus when the scroll has completed. + mRecView.addOnScrollListener( + new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged (RecyclerView view, int newState) { + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + // When scrolling stops, find the item and focus it. + RecyclerView.ViewHolder vh = + view.findViewHolderForAdapterPosition(pos); + if (vh != null) { + vh.itemView.requestFocus(); + } else { + // This might happen in weird corner cases, e.g. if the user is + // scrolling while a delete operation is in progress. In that + // case, just don't attempt to focus the missing item. + Log.w( + TAG, "Unable to focus position " + pos + " after a scroll"); + } + view.removeOnScrollListener(this); + } + } + }); + } + } + + } private final class ModelUpdateListener implements Model.UpdateListener { diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java index 8acf1af8713b..1bfc6e909c5a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java @@ -84,13 +84,7 @@ public abstract class DocumentHolder public boolean onKey(View v, int keyCode, KeyEvent event) { // Event listener should always be set. checkNotNull(mEventListener); - // Intercept enter key-up events, and treat them as clicks. Forward other events. - if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) { - return mEventListener.onActivate(this); - } else if (mKeyListener != null) { - return mKeyListener.onKey(v, keyCode, event); - } - return false; + return mEventListener.onKey(this, keyCode, event); } public void addEventListener(DocumentHolder.EventListener listener) { @@ -159,15 +153,29 @@ public abstract class DocumentHolder */ interface EventListener { /** + * Handles activation events on the document holder. + * * @param doc The target DocumentHolder * @return Whether the event was handled. */ public boolean onActivate(DocumentHolder doc); /** + * Handles selection events on the document holder. + * * @param doc The target DocumentHolder * @return Whether the event was handled. */ public boolean onSelect(DocumentHolder doc); + + /** + * Handles key events on the document holder. + * + * @param doc The target DocumentHolder. + * @param keyCode Key code for the event. + * @param event KeyEvent for the event. + * @return Whether the event was handled. + */ + public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java index eea91a015efd..516b25e6f572 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java @@ -34,7 +34,6 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -57,7 +56,7 @@ import java.util.Set; * Additionally it can be configured to restrict selection to a single element, @see * #setSelectMode. */ -public final class MultiSelectManager implements View.OnKeyListener { +public final class MultiSelectManager { /** Selection mode for multiple select. **/ public static final int MODE_MULTIPLE = 0; @@ -239,7 +238,8 @@ public final class MultiSelectManager implements View.OnKeyListener { } /** - * Clears the selection, without notifying anyone. + * Clears the selection, without notifying selection listeners. UI elements still need to be + * notified about state changes so that they can update their appearance. */ private void clearSelectionQuietly() { mRanger = null; @@ -248,10 +248,10 @@ public final class MultiSelectManager implements View.OnKeyListener { return; } - Selection intermediateSelection = getSelection(new Selection()); + Selection oldSelection = getSelection(new Selection()); mSelection.clear(); - for (String id: intermediateSelection.getAll()) { + for (String id: oldSelection.getAll()) { notifyItemStateChanged(id, false); } } @@ -334,21 +334,7 @@ public final class MultiSelectManager implements View.OnKeyListener { if (mSelection.contains(modelId)) { changed = attemptDeselect(modelId); } else { - boolean canSelect = notifyBeforeItemStateChange(modelId, true); - if (!canSelect) { - return; - } - if (mSingleSelect && hasSelection()) { - clearSelectionQuietly(); - } - - // Here we're already in selection mode. In that case - // When a simple click/tap (without SHIFT) creates causes - // an item to be selected. - // By recreating Ranger at this point, we allow the user to create - // multiple separate contiguous ranges with SHIFT+Click & Click. - selectAndNotify(modelId); - changed = true; + changed = attemptSelect(modelId); } if (changed) { @@ -357,9 +343,46 @@ public final class MultiSelectManager implements View.OnKeyListener { } /** - * Sets the magic location at which a selection range begins. This - * value is consulted when determining how to extend, and modify - * selection ranges. + * Handle a range selection event. + * <li> If the MSM is currently in single-select mode, only the last item in the range will + * actually be selected. + * <li>If a range selection is not already active, one will be started, and the given range of + * items will be selected. The given startPos becomes the anchor for the range selection. + * <li>If a range selection is already active, the anchor is not changed. The range is extended + * from its current anchor to endPos. + * + * @param startPos + * @param endPos + */ + public void selectRange(int startPos, int endPos) { + // In single-select mode, just select the last item in the range. + if (mSingleSelect) { + attemptSelect(mAdapter.getModelId(endPos)); + return; + } + + // In regular (i.e. multi-select) mode + if (!isRangeSelectionActive()) { + // If a range selection isn't active, start one up + attemptSelect(mAdapter.getModelId(startPos)); + setSelectionRangeBegin(startPos); + } + // Extend the range selection + mRanger.snapSelection(endPos); + notifySelectionChanged(); + } + + /** + * @return Whether or not there is a current range selection active. + */ + private boolean isRangeSelectionActive() { + return mRanger != null; + } + + /** + * Sets the magic location at which a selection range begins (the selection anchor). This value + * is consulted when determining how to extend, and modify selection ranges. Calling this when a + * range selection is active will reset the range selection. * * @throws IllegalStateException if {@code position} is not already be selected * @param position @@ -434,6 +457,24 @@ public final class MultiSelectManager implements View.OnKeyListener { } } + /** + * @param id + * @return True if the update was applied. + */ + private boolean attemptSelect(String id) { + checkArgument(id != null); + boolean canSelect = notifyBeforeItemStateChange(id, true); + if (!canSelect) { + return false; + } + if (mSingleSelect && hasSelection()) { + clearSelectionQuietly(); + } + + selectAndNotify(id); + return true; + } + private boolean notifyBeforeItemStateChange(String id, boolean nextState) { int lastListener = mCallbacks.size() - 1; for (int i = lastListener; i > -1; i--) { @@ -786,12 +827,10 @@ public final class MultiSelectManager implements View.OnKeyListener { Point createAbsolutePoint(Point relativePoint); Rect getAbsoluteRectForChildViewAt(int index); int getAdapterPositionAt(int index); - int getAdapterPositionForChildView(View view); int getColumnCount(); int getRowCount(); int getChildCount(); int getVisibleChildCount(); - void focusItem(int position); /** * Layout items are excluded from the GridModel. */ @@ -812,17 +851,8 @@ public final class MultiSelectManager implements View.OnKeyListener { } @Override - public int getAdapterPositionForChildView(View view) { - if (view.getParent() == mView) { - return mView.getChildAdapterPosition(view); - } else { - return RecyclerView.NO_POSITION; - } - } - - @Override public int getAdapterPositionAt(int index) { - return getAdapterPositionForChildView(mView.getChildAt(index)); + return mView.getChildAdapterPosition(mView.getChildAt(index)); } @Override @@ -921,39 +951,6 @@ public final class MultiSelectManager implements View.OnKeyListener { } @Override - public void focusItem(final int pos) { - // If the item is already in view, focus it; otherwise, scroll to it and focus it. - RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos); - if (vh != null) { - vh.itemView.requestFocus(); - } else { - mView.smoothScrollToPosition(pos); - // Set a one-time listener to request focus when the scroll has completed. - mView.addOnScrollListener( - new RecyclerView.OnScrollListener() { - @Override - public void onScrollStateChanged (RecyclerView view, int newState) { - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - // When scrolling stops, find the item and focus it. - RecyclerView.ViewHolder vh = - view.findViewHolderForAdapterPosition(pos); - if (vh != null) { - vh.itemView.requestFocus(); - } else { - // This might happen in weird corner cases, e.g. if the user is - // scrolling while a delete operation is in progress. In that - // case, just don't attempt to focus the missing item. - Log.w( - TAG, "Unable to focus position " + pos + " after a scroll"); - } - view.removeOnScrollListener(this); - } - } - }); - } - } - - @Override public boolean isLayoutItem(int pos) { // The band selection model only operates on documents and directories. Exclude other // types of adapter items (e.g. whitespace items like dividers). @@ -1907,99 +1904,4 @@ public final class MultiSelectManager implements View.OnKeyListener { return true; } } - - // TODO: Might have to move this to a more global level. e.g. What should happen if the - // user taps a file and then presses shift-down? Currently the RecyclerView never even sees - // the key event. Perhaps install a global key handler to catch those events while in - // selection mode? - @Override - public boolean onKey(View view, int keyCode, KeyEvent event) { - // Listen for key-down events. This allows the handler to respond appropriately when - // the user holds down the arrow keys for navigation. - if (event.getAction() != KeyEvent.ACTION_DOWN) { - return false; - } - - // Here we unpack information from the event and pass it to an more - // easily tested method....basically eliminating the need to synthesize - // events and views and so on in our tests. - int endPos = findTargetPosition(view, keyCode); - if (endPos == RecyclerView.NO_POSITION) { - // If there is no valid navigation target, don't handle the keypress. - return false; - } - - int startPos = mEnvironment.getAdapterPositionForChildView(view); - - return changeFocus(startPos, endPos, event.isShiftPressed()); - } - - /** - * @param startPosition The current focus position. - * @param targetPosition The adapter position to focus. - * @param extendSelection - */ - @VisibleForTesting - boolean changeFocus(int startPosition, int targetPosition, boolean extendSelection) { - // Focus the new file. - mEnvironment.focusItem(targetPosition); - - if (extendSelection) { - if (mSingleSelect) { - // We're in single select and have an existing selection. - // Our best guess as to what the user would expect is to advance the selection. - clearSelection(); - toggleSelection(targetPosition); - } else { - if (!hasSelection()) { - // No selection - start a selection when the user presses shift-arrow. - toggleSelection(startPosition); - setSelectionRangeBegin(startPosition); - } - mRanger.snapSelection(targetPosition); - notifySelectionChanged(); - } - } - - return true; - } - - /** - * Returns the adapter position that the key combo is targeted at. - */ - private int findTargetPosition(View view, int keyCode) { - int position = RecyclerView.NO_POSITION; - if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) { - position = 0; - } else if (keyCode == KeyEvent.KEYCODE_MOVE_END) { - position = mAdapter.getItemCount() - 1; - } else { - // Find a navigation target based on the arrow key that the user pressed. Ignore - // navigation targets that aren't items in the recycler view. - int searchDir = -1; - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - searchDir = View.FOCUS_UP; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - searchDir = View.FOCUS_DOWN; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - searchDir = View.FOCUS_LEFT; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - searchDir = View.FOCUS_RIGHT; - break; - } - if (searchDir != -1) { - View targetView = view.focusSearch(searchDir); - // TargetView can be null, for example, if the user pressed <down> at the bottom of - // the list. - if (targetView != null) { - position = mEnvironment.getAdapterPositionForChildView(targetView); - } - } - } - return position; - } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index 12c0b8fcd228..3f14a5506adf 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -176,6 +176,7 @@ public class RootInfo implements Durable, Parcelable { } else if (isExternalStorage()) { derivedIcon = R.drawable.ic_root_smartphone; derivedType = TYPE_LOCAL; + // TODO: Apply SD card icon to SD devices. } else if (isDownloads()) { derivedIcon = R.drawable.ic_root_download; derivedType = TYPE_DOWNLOADS; @@ -244,6 +245,10 @@ public class RootInfo implements Durable, Parcelable { || derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS; } + public boolean hasSettings() { + return (flags & Root.FLAG_HAS_SETTINGS) != 0; + } + public Drawable loadIcon(Context context) { if (derivedIcon != 0) { return context.getDrawable(derivedIcon); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java index ce5472580731..042ec85ad212 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java @@ -20,7 +20,6 @@ import static com.android.documentsui.StubProvider.ROOT_0_ID; import static com.android.documentsui.StubProvider.ROOT_1_ID; import android.support.test.uiautomator.UiObject; -import android.support.test.uiautomator.UiObjectNotFoundException; import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.LargeTest; @@ -31,9 +30,6 @@ public class SearchViewUiTest extends InstrumentationTestCase { private UiTestEnvironment mEnv; - private UiObject mDocsList; - private UiObject mMessageTextView; - @Override public void setUp() throws Exception { super.setUp(); @@ -151,4 +147,15 @@ public class SearchViewUiTest extends InstrumentationTestCase { mEnv.bot().openRoot(ROOT_0_ID); mEnv.assertDefaultContentOfTestDir0(); } + + public void testSearchIconVisible_RootWithSearchSupport() throws Exception { + mEnv.bot().openRoot(ROOT_0_ID); + mEnv.bot().assertSearchTextFiledAndIcon(false, true); + } + + public void testSearchIconHidden_RootNoSearchSupport() throws Exception { + mEnv.bot().openRoot(ROOT_1_ID); + mEnv.bot().assertSearchTextFiledAndIcon(false, false); + } + } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java index 980627b17e7c..98554276e71c 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java @@ -130,6 +130,11 @@ public class StubProvider extends DocumentsProvider { Log.i(TAG, "Created new root directory @ " + file.getPath()); } final RootInfo rootInfo = new RootInfo(file, getSize(rootId)); + + if(rootId.equals(ROOT_1_ID)) { + rootInfo.setSearchEnabled(false); + } + mStorage.put(rootInfo.document.documentId, rootInfo.document); mRoots.put(rootId, rootInfo); } @@ -152,8 +157,7 @@ public class StubProvider extends DocumentsProvider { final RootInfo info = entry.getValue(); final RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, id); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD - | Root.FLAG_SUPPORTS_SEARCH); + row.add(Root.COLUMN_FLAGS, info.flags); row.add(Root.COLUMN_TITLE, id); row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId); row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity()); @@ -705,22 +709,33 @@ public class StubProvider extends DocumentsProvider { } final static class RootInfo { + private static final int DEFAULT_ROOTS_FLAGS = Root.FLAG_SUPPORTS_SEARCH + | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD; + public final String name; public final StubDocument document; public long capacity; public long size; + public int flags; RootInfo(File file, long capacity) { this.name = file.getName(); this.capacity = 1024 * 1024; - this.document = StubDocument.createRootDocument(file, this); + this.flags = DEFAULT_ROOTS_FLAGS; this.capacity = capacity; this.size = 0; + this.document = StubDocument.createRootDocument(file, this); } public long getRemainingCapacity() { return capacity - size; } + + public void setSearchEnabled(boolean enabled) { + flags = enabled ? (flags | Root.FLAG_SUPPORTS_SEARCH) + : (flags & ~Root.FLAG_SUPPORTS_SEARCH); + } + } final static class StubDocument { diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java index f12ae106bb00..d609fa846591 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java @@ -150,7 +150,6 @@ class UiBot { void assertSearchTextFiledAndIcon(boolean searchTextFieldExists, boolean searchIconExists) { assertEquals(searchTextFieldExists, findSearchViewTextField().exists()); assertEquals(searchIconExists, findSearchViewIcon().exists()); - } void assertHasDocuments(String... labels) throws UiObjectNotFoundException { diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java index 16efc6e98174..87cd42f08079 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java @@ -22,6 +22,7 @@ import android.graphics.Rect; import android.os.SystemClock; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; @@ -130,5 +131,10 @@ public class DocumentHolderTest extends AndroidTestCase { return true; } + @Override + public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) { + return false; + } + } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java index d3ef9aa64ffb..b1cb29e775b5 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java @@ -173,12 +173,6 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertRangeSelection(0, 7); } - public void testKeyboardSelection() { - // This simulates shift-navigation. - keyToPosition(5, 10, true); - assertRangeSelection(5, 10); - } - public void testSingleSelectMode() { mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE); mManager.addCallback(mCallback); @@ -195,14 +189,6 @@ public class MultiSelectManagerTest extends AndroidTestCase { assertSelection(items.get(20)); } - public void testSingleSelectMode_ShiftDoesNotExtendSelection() { - mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE); - mManager.addCallback(mCallback); - longPress(20); - keyToPosition(20, 22, true); - assertSelection(items.get(22)); - } - public void testProvisionalSelection() { Selection s = mManager.getSelection(); assertSelection(); @@ -263,10 +249,6 @@ public class MultiSelectManagerTest extends AndroidTestCase { mManager.onSingleTapUp(TestInputEvent.shiftClick(position)); } - private void keyToPosition(int startPos, int endPos, boolean shift) { - mManager.changeFocus(startPos, endPos, shift); - } - private void assertSelected(String... expected) { for (int i = 0; i < expected.length; i++) { Selection selection = mManager.getSelection(); diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java index 7920c50fb9a8..353d4bdfb0b1 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java @@ -23,7 +23,6 @@ import android.graphics.Rect; import android.support.v7.widget.RecyclerView.OnScrollListener; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; -import android.view.View; import com.android.documentsui.dirlist.MultiSelectManager.GridModel; @@ -326,16 +325,6 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { } @Override - public int getAdapterPositionForChildView(View view) { - throw new UnsupportedOperationException(); - } - - @Override - public void focusItem(int i) { - throw new UnsupportedOperationException(); - } - - @Override public boolean isLayoutItem(int adapterPosition) { return false; } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java index 0e795615a137..8e624a036331 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java @@ -19,7 +19,6 @@ package com.android.documentsui.dirlist; import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.RecyclerView.OnScrollListener; -import android.view.View; import com.android.documentsui.dirlist.MultiSelectManager.SelectionEnvironment; @@ -83,11 +82,6 @@ public class TestSelectionEnvironment implements SelectionEnvironment { } @Override - public int getAdapterPositionForChildView(View view) { - return 0; - } - - @Override public int getColumnCount() { return 0; } @@ -108,10 +102,6 @@ public class TestSelectionEnvironment implements SelectionEnvironment { } @Override - public void focusItem(int position) { - } - - @Override public boolean isLayoutItem(int adapterPosition) { return false; } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 56e5a9b6c58d..f89934dc50e4 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -134,9 +134,6 @@ public class ExternalStorageProvider extends DocumentsProvider { final String rootId; final String title; if (volume.getType() == VolumeInfo.TYPE_EMULATED) { - // save off the primary volume for subsequent "Home" dir initialization. - primaryVolume = volume; - // We currently only support a single emulated volume mounted at // a time, and it's always considered the primary rootId = ROOT_ID_PRIMARY_EMULATED; @@ -167,9 +164,14 @@ public class ExternalStorageProvider extends DocumentsProvider { mRoots.put(rootId, root); root.rootId = rootId; - root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED + root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD; + if (volume.isPrimary()) { + // save off the primary volume for subsequent "Home" dir initialization. + primaryVolume = volume; + root.flags |= Root.FLAG_ADVANCED; + } // Dunno when this would NOT be the case, but never hurts to be correct. if (volume.isMountedWritable()) { root.flags |= Root.FLAG_SUPPORTS_CREATE; diff --git a/packages/PrintSpooler/res/color/item_text_color.xml b/packages/PrintSpooler/res/color/item_text_color.xml deleted file mode 100644 index f580fbd75577..000000000000 --- a/packages/PrintSpooler/res/color/item_text_color.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 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. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:color="#333333" /> - <item android:color="#888888"/> -</selector>
\ No newline at end of file diff --git a/packages/PrintSpooler/res/drawable/ic_info.xml b/packages/PrintSpooler/res/drawable/ic_info.xml index 2ecd1c79d23e..d64435b83e74 100644 --- a/packages/PrintSpooler/res/drawable/ic_info.xml +++ b/packages/PrintSpooler/res/drawable/ic_info.xml @@ -16,9 +16,9 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z" - android:fillColor="#757575"/> + android:fillColor="@android:color/white" + android:pathData="M11,17l2,0l0,-6l-2,0l0,6.0zm1,-15.0C6.48,2 2,6.48 2,12.0s4.48,10 10,10 10,-4.48 10,-10.0S17.52,2 12,2.0zm0,18.0c-4.41,0 -8,-3.59 -8,-8.0s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8.0zM11,9l2,0L13,7l-2,0l0,2.0z"/> </vector>
\ No newline at end of file diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml index e0efbc42a627..defbf8db0026 100644 --- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml @@ -28,7 +28,6 @@ android:layout_width="32dip" android:layout_height="32dip" android:layout_gravity="center_vertical" - android:layout_marginEnd="8dip" android:duplicateParentState="true" android:contentDescription="@null" android:visibility="invisible"> @@ -38,6 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + android:layout_marginStart="8dip" android:duplicateParentState="true"> <TextView diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml index 50f44c21656c..1209aa6f0fa2 100644 --- a/packages/PrintSpooler/res/layout/printer_list_item.xml +++ b/packages/PrintSpooler/res/layout/printer_list_item.xml @@ -25,20 +25,22 @@ <ImageView android:id="@+id/icon" - android:layout_width="32dip" - android:layout_height="32dip" + android:layout_width="40dip" + android:layout_height="40dip" android:layout_gravity="center_vertical" - android:layout_marginEnd="8dip" + android:layout_marginTop="8dip" + android:layout_marginBottom="8dip" android:duplicateParentState="true" android:contentDescription="@null" android:visibility="invisible"> </ImageView> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" + <RelativeLayout + android:layout_width="0dip" android:layout_weight="1" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="16dip" android:duplicateParentState="true"> <TextView @@ -49,7 +51,10 @@ android:singleLine="true" android:ellipsize="end" android:textIsSelectable="false" - android:gravity="top|start" + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" + android:fadingEdge="horizontal" + android:textAlignment="viewStart" android:textColor="?android:attr/textColorPrimary" android:duplicateParentState="true"> </TextView> @@ -58,24 +63,30 @@ android:id="@+id/subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_below="@id/title" + android:layout_alignParentStart="true" android:textAppearance="?android:attr/textAppearanceSmall" android:singleLine="true" android:ellipsize="end" android:textIsSelectable="false" android:visibility="gone" android:textColor="?android:attr/textColorSecondary" + android:textAlignment="viewStart" android:duplicateParentState="true"> </TextView> - </LinearLayout> + </RelativeLayout> <ImageView android:id="@+id/more_info" - android:layout_width="24dip" - android:layout_height="24dip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_gravity="center_vertical" + android:paddingLeft="16dip" android:contentDescription="@string/printer_info_desc" android:src="@drawable/ic_info" + android:tint="?android:attr/colorControlNormal" + android:tintMode="src_in" android:visibility="gone"> </ImageView> diff --git a/packages/PrintSpooler/res/values-ca/arrays.xml b/packages/PrintSpooler/res/values-ca/arrays.xml deleted file mode 100644 index c1b149c59960..000000000000 --- a/packages/PrintSpooler/res/values-ca/arrays.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 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. ---> -<resources> - - <string-array name="pdf_printer_media_sizes" translatable="false"> - <item>NA_LETTER</item> - <item>NA_GOVT_LETTER</item> - <item>NA_LEGAL</item> - <item>NA_JUNIOR_LEGAL</item> - <item>NA_LEDGER</item> - <item>NA_TABLOID</item> - <item>NA_INDEX_3X5</item> - <item>NA_INDEX_4X6</item> - <item>NA_INDEX_5X8</item> - <item>NA_MONARCH</item> - <item>NA_QUARTO</item> - <item>NA_FOOLSCAP</item> - </string-array> - -</resources> diff --git a/packages/PrintSpooler/res/values-en-rCA/arrays.xml b/packages/PrintSpooler/res/values-en-rCA/arrays.xml deleted file mode 100644 index d40278c3973d..000000000000 --- a/packages/PrintSpooler/res/values-en-rCA/arrays.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 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. ---> -<resources> - - <string-array name="pdf_printer_media_sizes" translatable="false"> - <item>NA_LETTER</item> - <item>NA_GOVT_LETTER</item> - <item>NA_LEGAL</item> - <item>NA_JUNIOR_LEGAL</item> - <item>NA_LEDGER</item> - <item>NA_TABLOID</item> - <item>NA_INDEX_3X5</item> - <item>NA_INDEX_4X6</item> - <item>NA_INDEX_5X8</item> - <item>NA_MONARCH</item> - <item>NA_QUARTO</item> - <item>NA_FOOLSCAP</item> - </string-array> - -</resources> diff --git a/packages/PrintSpooler/res/values-en-rUS/arrays.xml b/packages/PrintSpooler/res/values-en-rUS/arrays.xml deleted file mode 100644 index d40278c3973d..000000000000 --- a/packages/PrintSpooler/res/values-en-rUS/arrays.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 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. ---> -<resources> - - <string-array name="pdf_printer_media_sizes" translatable="false"> - <item>NA_LETTER</item> - <item>NA_GOVT_LETTER</item> - <item>NA_LEGAL</item> - <item>NA_JUNIOR_LEGAL</item> - <item>NA_LEDGER</item> - <item>NA_TABLOID</item> - <item>NA_INDEX_3X5</item> - <item>NA_INDEX_4X6</item> - <item>NA_INDEX_5X8</item> - <item>NA_MONARCH</item> - <item>NA_QUARTO</item> - <item>NA_FOOLSCAP</item> - </string-array> - -</resources> diff --git a/packages/PrintSpooler/res/values-es-rUS/arrays.xml b/packages/PrintSpooler/res/values-es-rUS/arrays.xml deleted file mode 100644 index c1b149c59960..000000000000 --- a/packages/PrintSpooler/res/values-es-rUS/arrays.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 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. ---> -<resources> - - <string-array name="pdf_printer_media_sizes" translatable="false"> - <item>NA_LETTER</item> - <item>NA_GOVT_LETTER</item> - <item>NA_LEGAL</item> - <item>NA_JUNIOR_LEGAL</item> - <item>NA_LEDGER</item> - <item>NA_TABLOID</item> - <item>NA_INDEX_3X5</item> - <item>NA_INDEX_4X6</item> - <item>NA_INDEX_5X8</item> - <item>NA_MONARCH</item> - <item>NA_QUARTO</item> - <item>NA_FOOLSCAP</item> - </string-array> - -</resources> diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml deleted file mode 100644 index 3187cbefaa1e..000000000000 --- a/packages/PrintSpooler/res/values-ja/arrays.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 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. ---> -<resources> - - <string-array name="pdf_printer_media_sizes" translatable="false"> - <item>JIS_B10</item> - <item>JIS_B9</item> - <item>JIS_B8</item> - <item>JIS_B7</item> - <item>JIS_B6</item> - <item>JIS_B5</item> - <item>JIS_B4</item> - <item>JIS_B3</item> - <item>JIS_B2</item> - <item>JIS_B1</item> - <item>JIS_B0</item> - <item>JIS_EXEC</item> - <item>JPN_CHOU4</item> - <item>JPN_CHOU3</item> - <item>JPN_CHOU2</item> - <item>JPN_HAGAKI</item> - <item>JPN_OUFUKU</item> - <item>JPN_KAHU</item> - <item>JPN_KAKU2</item> - <item>JPN_YOU4</item> - </string-array> - -</resources> diff --git a/packages/PrintSpooler/res/values/arrays.xml b/packages/PrintSpooler/res/values/arrays.xml index afe3c7122dd7..8658be46c6eb 100644 --- a/packages/PrintSpooler/res/values/arrays.xml +++ b/packages/PrintSpooler/res/values/arrays.xml @@ -121,38 +121,4 @@ <!-- Everything else is ISO --> </string-array> - <string-array name="pdf_printer_media_sizes" translatable="false"> - <item>ISO_A0</item> - <item>ISO_A1</item> - <item>ISO_A2</item> - <item>ISO_A3</item> - <item>ISO_A4</item> - <item>ISO_A5</item> - <item>ISO_A6</item> - <item>ISO_A7</item> - <item>ISO_A8</item> - <item>ISO_A9</item> - <item>ISO_A10</item> - <item>ISO_B1</item> - <item>ISO_B2</item> - <item>ISO_B3</item> - <item>ISO_B4</item> - <item>ISO_B5</item> - <item>ISO_B6</item> - <item>ISO_B7</item> - <item>ISO_B8</item> - <item>ISO_B9</item> - <item>ISO_B10</item> - <item>ISO_C1</item> - <item>ISO_C2</item> - <item>ISO_C3</item> - <item>ISO_C4</item> - <item>ISO_C5</item> - <item>ISO_C6</item> - <item>ISO_C7</item> - <item>ISO_C8</item> - <item>ISO_C9</item> - <item>ISO_C10</item> - </string-array> - </resources> diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index 892be2d06fce..a1ea658e5aff 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -61,7 +61,9 @@ import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.text.TextWatcher; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; +import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -2252,10 +2254,17 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); if (icon != null) { - iconView.setImageDrawable(icon); iconView.setVisibility(View.VISIBLE); + if (!isEnabled(position)) { + icon.mutate(); + + TypedValue value = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true); + icon.setAlpha((int)(value.getFloat() * 255)); + } + iconView.setImageDrawable(icon); } else { - iconView.setVisibility(View.INVISIBLE); + iconView.setVisibility(View.GONE); } return convertView; @@ -2353,6 +2362,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private PrinterInfo createFakePdfPrinter() { + ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes(); MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this); PrinterId printerId = new PrinterId(getComponentName(), "PDF printer"); @@ -2360,11 +2370,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat PrinterCapabilitiesInfo.Builder builder = new PrinterCapabilitiesInfo.Builder(printerId); - String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes); - final int mediaSizeIdCount = mediaSizeIds.length; - for (int i = 0; i < mediaSizeIdCount; i++) { - String id = mediaSizeIds[i]; - MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id); + final int mediaSizeCount = allMediaSizes.size(); + for (int i = 0; i < mediaSizeCount; i++) { + MediaSize mediaSize = allMediaSizes.valueAt(i); builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize)); } @@ -2442,6 +2450,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } updateOptionsUi(); + updateSummary(); } private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities, diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java index 1aec2531e2ca..4f7624adc9ad 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java @@ -37,6 +37,7 @@ import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.database.DataSetObserver; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -47,6 +48,7 @@ import android.printservice.PrintServiceInfo; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.util.TypedValue; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; @@ -544,7 +546,10 @@ public final class SelectPrinterActivity extends Activity { final int printerCount = mPrinters.size(); for (int i = 0; i < printerCount; i++) { PrinterInfo printer = mPrinters.get(i); - if (printer.getName().toLowerCase().contains(constraintLowerCase)) { + String description = printer.getDescription(); + if (printer.getName().toLowerCase().contains(constraintLowerCase) + || description != null && description.toLowerCase() + .contains(constraintLowerCase)) { filteredPrinters.add(printer); } } @@ -663,18 +668,28 @@ public final class SelectPrinterActivity extends Activity { @Override public void onClick(View v) { try { - startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0, 0); + startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0, + 0); } catch (SendIntentException e) { Log.e(LOG_TAG, "Could not execute pending info intent: %s", e); } } }); + } else { + moreInfoView.setVisibility(View.GONE); } ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); if (icon != null) { - iconView.setImageDrawable(icon); iconView.setVisibility(View.VISIBLE); + if (!isActionable(position)) { + icon.mutate(); + + TypedValue value = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true); + icon.setAlpha((int)(value.getFloat() * 255)); + } + iconView.setImageDrawable(icon); } else { iconView.setVisibility(View.GONE); } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java index 56c4edb7f377..5ffa581c2de3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java @@ -141,6 +141,16 @@ public class SettingsDrawerActivity extends Activity { mCategoryListeners.remove(listener); } + public void setIsDrawerPresent(boolean isPresent) { + if (isPresent) { + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + updateDrawer(); + } else { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + mDrawerLayout = null; + } + } + public void openDrawer() { if (mDrawerLayout != null) { mDrawerLayout.openDrawer(Gravity.START); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 2e96f18bedb2..b270dd86bb8e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -730,6 +730,11 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { int stateVersion = dataInput.readInt(); + if (stateVersion > STATE_VERSION) { + // Constrain the maximum state version this backup agent + // can handle in case a newer or corrupt backup set existed + stateVersion = STATE_VERSION; + } for (int i = 0; i < STATE_SIZES[stateVersion]; i++) { stateChecksums[i] = dataInput.readLong(); } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2713fb51991c..c74e4112dada 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -349,6 +349,12 @@ android:resizeable="true" android:supportsPictureInPicture="true" android:excludeFromRecents="true" /> + <activity + android:name="com.android.systemui.tv.pip.PipOnboardingActivity" + android:exported="true" + android:theme="@style/PipTheme" + android:launchMode="singleTop" + android:excludeFromRecents="true" /> <!-- platform logo easter egg activity --> <activity diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml index a638d175c341..3562c644e61e 100644 --- a/packages/SystemUI/res/layout/tv_pip_menu.xml +++ b/packages/SystemUI/res/layout/tv_pip_menu.xml @@ -17,45 +17,37 @@ */ --> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <FrameLayout - android:layout_alignParentEnd="true" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:paddingStart="10dp" - android:paddingEnd="10dp" - android:background="#88FFFFFF"> - <LinearLayout - android:orientation="vertical" - android:layout_gravity="center_vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" > +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="end" + android:paddingStart="10dp" + android:paddingEnd="10dp" + android:background="#88FFFFFF" + android:gravity="center_vertical" > - <Button android:id="@+id/full" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/pip_fullscreen" - android:textSize="10sp" - android:focusable="true" /> + <Button android:id="@+id/full" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/pip_fullscreen" + android:textSize="10sp" + android:focusable="true" /> - <Button android:id="@+id/exit" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/pip_exit" - android:textSize="10sp" - android:focusable="true" /> + <Button android:id="@+id/exit" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/pip_exit" + android:textSize="10sp" + android:focusable="true" /> - <Button android:id="@+id/cancel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/pip_cancel" - android:textSize="10sp" - android:focusable="true" /> - </LinearLayout> - </FrameLayout> -</RelativeLayout> + <Button android:id="@+id/cancel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/pip_cancel" + android:textSize="10sp" + android:focusable="true" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_onboarding.xml b/packages/SystemUI/res/layout/tv_pip_onboarding.xml new file mode 100644 index 000000000000..ef395558af64 --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_onboarding.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2016, 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. +*/ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#C00288D1" + android:gravity="center" + android:orientation="vertical" > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="30sp" + android:textColor="@android:color/white" + android:text="@string/pip_onboarding_title" /> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_sysbar_home" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="30dp" + android:textSize="13sp" + android:textColor="@android:color/white" + android:text="@string/pip_onboarding_description" /> + <Button + android:id="@+id/close" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="15sp" + android:textAllCaps="true" + android:text="@string/pip_onboarding_button" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml index e8691b511706..6d9c48d44dc6 100644 --- a/packages/SystemUI/res/layout/tv_pip_overlay.xml +++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml @@ -17,17 +17,13 @@ */ --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/guide_overlay" android:layout_width="match_parent" - android:layout_height="match_parent"> - <TextView - android:id="@+id/guide_overlay" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom" - android:padding="3dp" - android:textSize="13sp" - android:textColor="#111111" - android:background="#99EEEEEE" - android:text="@string/pip_hold_home" /> -</FrameLayout> + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:padding="3dp" + android:textSize="13sp" + android:textColor="#111111" + android:background="#99EEEEEE" + android:text="@string/pip_hold_home" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index ebe0d973e752..e8df01b19415 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -159,18 +159,6 @@ in from the bottom of the screen. --> <integer name="recents_enter_from_home_transition_duration">100</integer> - <!-- The duration for animating the task from the bottom of the screen when transitioning - from home. --> - <integer name="recents_task_enter_from_home_duration">225</integer> - - <!-- The stagger for each task when animating the task from the bottom of the screen when - transitioning from home. --> - <integer name="recents_task_enter_from_home_stagger_delay">12</integer> - - <!-- The duration of the animation of the tasks to the bottom of the screen when leaving - Recents to go back to the Launcher. --> - <integer name="recents_task_exit_to_home_duration">225</integer> - <!-- The min animation duration for animating the nav bar scrim in. --> <integer name="recents_nav_bar_scrim_enter_duration">400</integer> diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml index c64327de65da..7c4768d29cfe 100644 --- a/packages/SystemUI/res/values/strings_tv.xml +++ b/packages/SystemUI/res/values/strings_tv.xml @@ -17,6 +17,8 @@ */ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Picture-in-Picture menu --> + <eat-comment /> <!-- Button to close PIP on PIP UI --> <string name="pip_exit" translatable="false">Close PIP</string> <!-- Button to move PIP screen to the fullscreen on PIP UI --> @@ -29,4 +31,13 @@ <string name="pip_cancel" translatable="false">Cancel</string> <!-- Overlay text on PIP --> <string name="pip_hold_home" translatable="false">Hold HOME to control PIP</string> + + <!-- Picture-in-Picture onboarding screen --> + <eat-comment /> + <!-- Title for onboarding screen. --> + <string name="pip_onboarding_title" translatable="false">Picture-in-picture</string> + <!-- Description for onboarding screen. --> + <string name="pip_onboarding_description" translatable="false">Press and hold the HOME\nbutton to close or control it</string> + <!-- Button to close onboarding screen. --> + <string name="pip_onboarding_button" translatable="false">Got it</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 7933cc692f02..e2b377737dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -43,6 +43,7 @@ public final class Prefs { Key.DND_FAVORITE_BUCKET_INDEX, Key.DND_NONE_SELECTED, Key.DND_FAVORITE_ZEN, + Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, }) public @interface Key { String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId"; @@ -58,6 +59,7 @@ public final class Prefs { String DND_FAVORITE_BUCKET_INDEX = "DndCountdownMinuteIndex"; String DND_NONE_SELECTED = "DndNoneSelected"; String DND_FAVORITE_ZEN = "DndFavoriteZen"; + String TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN = "TvPictureInPictureOnboardingShown"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java index e288878a15ff..ee3eb02c8a38 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java @@ -39,7 +39,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.views.TaskViewAnimation; +import com.android.systemui.recents.views.AnimationProps; import java.util.ArrayList; import java.util.Calendar; @@ -224,7 +224,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd if (row.getViewType() == TASK_ROW_VIEW_TYPE) { TaskRow taskRow = (TaskRow) row; Task task = taskRow.task; - mStack.removeTask(task, TaskViewAnimation.IMMEDIATE); + mStack.removeTask(task, AnimationProps.IMMEDIATE); EventBus.getDefault().send(new DeleteTaskDataEvent(task)); i = removeTaskRow(i); } @@ -304,7 +304,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd public void onTaskRemoved(Task task, int position) { // Since this is removed from the history, we need to update the stack as well to ensure // that the model is correct. Since the stack is hidden, we can update it immediately. - mStack.removeTask(task, TaskViewAnimation.IMMEDIATE); + mStack.removeTask(task, AnimationProps.IMMEDIATE); removeTaskRow(position); if (mRows.isEmpty()) { dismissHistory(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index aa8efa711550..1f91dceb3e99 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -43,7 +43,7 @@ import com.android.systemui.recents.misc.NamedCounter; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.views.DropTarget; -import com.android.systemui.recents.views.TaskViewAnimation; +import com.android.systemui.recents.views.AnimationProps; import java.util.ArrayList; import java.util.Collections; @@ -228,12 +228,12 @@ public class TaskStack { * Notifies when a task has been removed from the stack. */ void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, - Task newFrontMostTask, TaskViewAnimation animation); + Task newFrontMostTask, AnimationProps animation); /** * Notifies when a task has been removed from the history. */ - void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation); + void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation); } /** @@ -531,7 +531,7 @@ public class TaskStack { * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on * how they should update themselves. */ - public void removeTask(Task t, TaskViewAnimation animation) { + public void removeTask(Task t, AnimationProps animation) { if (mStackTaskList.contains(t)) { boolean wasFrontMostTask = (getStackFrontMostTask(false /* includeFreeform */) == t); removeTaskImpl(mStackTaskList, t); @@ -575,7 +575,7 @@ public class TaskStack { if (!newTasksMap.containsKey(task.key)) { if (notifyStackChanges) { mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null, - TaskViewAnimation.IMMEDIATE); + AnimationProps.IMMEDIATE); } } task.setGroup(null); diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java index 2d4174296e7b..58ec8521ec7b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java @@ -28,7 +28,7 @@ import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.model.TaskStack.TaskStackCallbacks; -import com.android.systemui.recents.views.TaskViewAnimation; +import com.android.systemui.recents.views.AnimationProps; import java.util.ArrayList; import java.util.List; @@ -137,7 +137,7 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T @Override public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, - Task newFrontMostTask, TaskViewAnimation animation) { + Task newFrontMostTask, AnimationProps animation) { getAdapter().notifyItemRemoved(stack.getStackTasks().indexOf(removedTask)); if (mFocusedTask == removedTask) { resetFocusedTask(removedTask); @@ -152,7 +152,7 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T } @Override - public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation) { + public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation) { //No history task on tv } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java new file mode 100644 index 000000000000..93878c52d6de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016 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.systemui.recents.views; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.annotation.IntDef; +import android.util.SparseArray; +import android.util.SparseLongArray; +import android.view.View; +import android.view.animation.Interpolator; + +import com.android.systemui.Interpolators; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * The generic set of animation properties to animate a {@link View}. The animation can have + * different interpolators, start delays and durations for each of the different properties. + */ +public class AnimationProps { + + public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR); + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS}) + public @interface PropType {} + + public static final int ALL = 0; + public static final int TRANSLATION_X = 1; + public static final int TRANSLATION_Y = 2; + public static final int TRANSLATION_Z = 3; + public static final int ALPHA = 4; + public static final int SCALE = 5; + public static final int BOUNDS = 6; + + private SparseLongArray mPropStartDelay; + private SparseLongArray mPropDuration; + private SparseArray<Interpolator> mPropInterpolators; + private Animator.AnimatorListener mListener; + + /** + * The builder constructor. + */ + public AnimationProps() {} + + /** + * Creates an animation with a default {@param duration} and {@param interpolator} for all + * properties in this animation. + */ + public AnimationProps(int duration, Interpolator interpolator) { + this(0, duration, interpolator, null); + } + + /** + * Creates an animation with a default {@param duration} and {@param interpolator} for all + * properties in this animation. + */ + public AnimationProps(int duration, Interpolator interpolator, + Animator.AnimatorListener listener) { + this(0, duration, interpolator, listener); + } + + /** + * Creates an animation with a default {@param startDelay}, {@param duration} and + * {@param interpolator} for all properties in this animation. + */ + public AnimationProps(int startDelay, int duration, Interpolator interpolator) { + this(startDelay, duration, interpolator, null); + } + + /** + * Creates an animation with a default {@param startDelay}, {@param duration} and + * {@param interpolator} for all properties in this animation. + */ + public AnimationProps(int startDelay, int duration, Interpolator interpolator, + Animator.AnimatorListener listener) { + setStartDelay(ALL, startDelay); + setDuration(ALL, duration); + setInterpolator(ALL, interpolator); + setListener(listener); + } + + /** + * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to + * manually apply the individual animation properties for each of the animators respectively. + */ + public AnimatorSet createAnimator(List<Animator> animators) { + AnimatorSet anim = new AnimatorSet(); + if (mListener != null) { + anim.addListener(mListener); + } + anim.playTogether(animators); + return anim; + } + + /** + * Applies the specific start delay, duration and interpolator to the given {@param animator} + * for the specified {@param propertyType}. + */ + public <T extends Animator> T apply(@PropType int propertyType, T animator) { + animator.setStartDelay(getStartDelay(propertyType)); + animator.setDuration(getDuration(propertyType)); + animator.setInterpolator(getInterpolator(propertyType)); + return animator; + } + + /** + * Sets a start delay for a specific property. + */ + public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) { + if (mPropStartDelay == null) { + mPropStartDelay = new SparseLongArray(); + } + mPropStartDelay.append(propertyType, startDelay); + return this; + } + + /** + * Returns the start delay for a specific property. + */ + public long getStartDelay(@PropType int propertyType) { + if (mPropStartDelay != null) { + long startDelay = mPropStartDelay.get(propertyType, -1); + if (startDelay != -1) { + return startDelay; + } + return mPropStartDelay.get(ALL, 0); + } + return 0; + } + + /** + * Sets a duration for a specific property. + */ + public AnimationProps setDuration(@PropType int propertyType, int duration) { + if (mPropDuration == null) { + mPropDuration = new SparseLongArray(); + } + mPropDuration.append(propertyType, duration); + return this; + } + + /** + * Returns the duration for a specific property. + */ + public long getDuration(@PropType int propertyType) { + if (mPropDuration != null) { + long duration = mPropDuration.get(propertyType, -1); + if (duration != -1) { + return duration; + } + return mPropDuration.get(ALL, 0); + } + return 0; + } + + /** + * Sets an interpolator for a specific property. + */ + public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) { + if (mPropInterpolators == null) { + mPropInterpolators = new SparseArray<>(); + } + mPropInterpolators.append(propertyType, interpolator); + return this; + } + + /** + * Returns the interpolator for a specific property, falling back to the general interpolator + * if there is no specific property interpolator. + */ + public Interpolator getInterpolator(@PropType int propertyType) { + if (mPropInterpolators != null) { + Interpolator interp = mPropInterpolators.get(propertyType); + if (interp != null) { + return interp; + } + return mPropInterpolators.get(ALL, Interpolators.LINEAR); + } + return Interpolators.LINEAR; + } + + /** + * Sets an animator listener for this animation. + */ + public AnimationProps setListener(Animator.AnimatorListener listener) { + mListener = listener; + return this; + } + + /** + * Returns the animator listener for this animation. + */ + public Animator.AnimatorListener getListener() { + return mListener; + } + + /** + * Returns whether this animation has any duration. + */ + public boolean isImmediate() { + int count = mPropDuration.size(); + for (int i = 0; i < count; i++) { + if (mPropDuration.valueAt(i) > 0) { + return false; + } + } + return true; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index e2ff52cefd23..5e113b997bce 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -497,8 +497,9 @@ public class RecentsView extends FrameLayout { } @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); + public void onDrawForeground(Canvas canvas) { + super.onDrawForeground(canvas); + ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates(); for (int i = visDockStates.size() - 1; i >= 0; i--) { Drawable d = visDockStates.get(i).viewState.dockAreaOverlay; @@ -530,8 +531,7 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { // Hide the history button - int taskViewExitToHomeDuration = getResources().getInteger( - R.integer.recents_task_exit_to_home_duration); + int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; hideHistoryButton(taskViewExitToHomeDuration, false /* translate */); animateBackgroundScrim(0f, taskViewExitToHomeDuration); } @@ -588,7 +588,7 @@ public class RecentsView extends FrameLayout { tmpTransform.scale = 1f; tmpTransform.rect.set(taskViewRect); mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform, - new TaskViewAnimation(125, Interpolators.ALPHA_OUT, + new AnimationProps(125, Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -598,7 +598,7 @@ public class RecentsView extends FrameLayout { event.task.key.id, dockState.createMode); // Animate the stack accordingly - TaskViewAnimation stackAnim = new TaskViewAnimation( + AnimationProps stackAnim = new AnimationProps( TaskStackView.DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN); mTaskStackView.getStack().removeTask(event.task, stackAnim); @@ -646,9 +646,8 @@ public class RecentsView extends FrameLayout { public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); if (!launchState.launchedFromAppWithThumbnail && mStack.getTaskCount() > 0) { - int taskViewEnterFromHomeDuration = getResources().getInteger( - R.integer.recents_task_enter_from_home_duration); - animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, taskViewEnterFromHomeDuration); + animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, + TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 682c29820ce3..0eae183e58d6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -20,8 +20,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Resources; +import android.graphics.Path; import android.graphics.RectF; import android.view.View; +import android.view.animation.PathInterpolator; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -63,6 +65,22 @@ public class TaskStackAnimationHelper { ReferenceCountedTrigger postAnimationTrigger); } + private static final int FRAME_OFFSET_MS = 16; + + public static final int ENTER_FROM_HOME_ALPHA_DURATION = 100; + public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 333; + private static final PathInterpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR = + new PathInterpolator(0, 0, 0, 1f); + private static final PathInterpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = + new PathInterpolator(0, 0, 0.2f, 1f); + + public static final int EXIT_TO_HOME_ALPHA_DURATION = 100; + public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 150; + private static final PathInterpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR = + new PathInterpolator(0.8f, 0, 0.6f, 1f); + private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR = + new PathInterpolator(0.4f, 0, 1f, 1f); + private TaskStackView mStackView; private TaskViewTransform mTmpTransform = new TaskViewTransform(); @@ -157,15 +175,12 @@ public class TaskStackAnimationHelper { R.integer.recents_task_enter_from_app_duration); int taskViewEnterFromAffiliatedAppDuration = res.getInteger( R.integer.recents_task_enter_from_affiliated_app_duration); - int taskViewEnterFromHomeDuration = res.getInteger( - R.integer.recents_task_enter_from_home_duration); - int taskViewEnterFromHomeStaggerDelay = res.getInteger( - R.integer.recents_task_enter_from_home_stagger_delay); // Create enter animations for each of the views from front to back List<TaskView> taskViews = mStackView.getTaskViews(); int taskViewCount = taskViews.size(); for (int i = taskViewCount - 1; i >= 0; i--) { + int taskIndexFromFront = taskViewCount - i - 1; final TaskView tv = taskViews.get(i); Task task = tv.getTask(); boolean currentTaskOccludesLaunchTarget = false; @@ -186,7 +201,7 @@ public class TaskStackAnimationHelper { } else { // Animate the task up if it was occluding the launch target if (currentTaskOccludesLaunchTarget) { - TaskViewAnimation taskAnimation = new TaskViewAnimation( + AnimationProps taskAnimation = new AnimationProps( taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN, new AnimatorListenerAdapter() { @Override @@ -202,14 +217,16 @@ public class TaskStackAnimationHelper { } else if (launchState.launchedFromHome) { // Animate the tasks up - int frontIndex = (taskViewCount - i - 1); - int delay = frontIndex * taskViewEnterFromHomeStaggerDelay; - int duration = taskViewEnterFromHomeDuration + - frontIndex * taskViewEnterFromHomeStaggerDelay; - - TaskViewAnimation taskAnimation = new TaskViewAnimation(delay, - duration, Interpolators.DECELERATE_QUINT, - postAnimationTrigger.decrementOnAnimationEnd()); + AnimationProps taskAnimation = new AnimationProps() + .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS) + .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION) + .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION - + (taskIndexFromFront * FRAME_OFFSET_MS)) + .setInterpolator(AnimationProps.BOUNDS, + ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR) + .setInterpolator(AnimationProps.ALPHA, + ENTER_FROM_HOME_ALPHA_INTERPOLATOR) + .setListener(postAnimationTrigger.decrementOnAnimationEnd()); postAnimationTrigger.increment(); mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation); } @@ -221,7 +238,6 @@ public class TaskStackAnimationHelper { */ public void startExitToHomeAnimation(boolean animated, ReferenceCountedTrigger postAnimationTrigger) { - Resources res = mStackView.getResources(); TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm(); TaskStackViewScroller stackScroller = mStackView.getScroller(); TaskStack stack = mStackView.getStack(); @@ -232,19 +248,32 @@ public class TaskStackAnimationHelper { } int offscreenY = stackLayout.mStackRect.bottom; - int taskViewExitToHomeDuration = res.getInteger( - R.integer.recents_task_exit_to_home_duration); // Create the animations for each of the tasks List<TaskView> taskViews = mStackView.getTaskViews(); int taskViewCount = taskViews.size(); for (int i = 0; i < taskViewCount; i++) { + int taskIndexFromFront = taskViewCount - i - 1; TaskView tv = taskViews.get(i); Task task = tv.getTask(); - TaskViewAnimation taskAnimation = new TaskViewAnimation( - animated ? taskViewExitToHomeDuration : 0, Interpolators.FAST_OUT_LINEAR_IN, - postAnimationTrigger.decrementOnAnimationEnd()); - postAnimationTrigger.increment(); + + // Animate the tasks down + AnimationProps taskAnimation; + if (animated) { + taskAnimation = new AnimationProps() + .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS) + .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION) + .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION + + (taskIndexFromFront * FRAME_OFFSET_MS)) + .setInterpolator(AnimationProps.BOUNDS, + EXIT_TO_HOME_TRANSLATION_INTERPOLATOR) + .setInterpolator(AnimationProps.ALPHA, + EXIT_TO_HOME_ALPHA_INTERPOLATOR) + .setListener(postAnimationTrigger.decrementOnAnimationEnd()); + postAnimationTrigger.increment(); + } else { + taskAnimation = AnimationProps.IMMEDIATE; + } stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform, null); @@ -283,7 +312,7 @@ public class TaskStackAnimationHelper { screenPinningRequested, postAnimationTrigger); } else if (currentTaskOccludesLaunchTarget) { // Animate this task out of view - TaskViewAnimation taskAnimation = new TaskViewAnimation( + AnimationProps taskAnimation = new AnimationProps( taskViewExitToAppDuration, Interpolators.ALPHA_OUT, postAnimationTrigger.decrementOnAnimationEnd()); postAnimationTrigger.increment(); @@ -315,7 +344,7 @@ public class TaskStackAnimationHelper { deleteTaskView.setClipViewInStack(false); // Compose the new animation and transform and star the animation - TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration, + AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration, Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -352,7 +381,7 @@ public class TaskStackAnimationHelper { for (int i = taskViewCount - 1; i >= 0; i--) { TaskView tv = taskViews.get(i); Task task = tv.getTask(); - TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i, + AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i, historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN, postAnimationTrigger.decrementOnAnimationEnd()); postAnimationTrigger.increment(); @@ -381,7 +410,7 @@ public class TaskStackAnimationHelper { int taskViewCount = taskViews.size(); for (int i = taskViewCount - 1; i >= 0; i--) { TaskView tv = taskViews.get(i); - TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i, + AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i, historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN); stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(), mTmpTransform, null); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 232b41648748..1c97b5a22ec7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -16,6 +16,10 @@ package com.android.systemui.recents.views; +import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; + import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.ComponentName; @@ -34,9 +38,9 @@ import android.util.MutableBoolean; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.systemui.Interpolators; @@ -85,10 +89,6 @@ import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; import java.util.List; -import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - /* The visual representation of a task stack view */ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, @@ -124,7 +124,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskView> mTaskViews = new ArrayList<>(); ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); - TaskViewAnimation mDeferredTaskViewLayoutAnimation = null; + AnimationProps mDeferredTaskViewLayoutAnimation = null; DozeTrigger mUIDozeTrigger; Task mFocusedTask; @@ -135,6 +135,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal boolean mTaskViewsClipDirty = true; boolean mAwaitingFirstLayout = true; + boolean mInMeasureLayout = false; boolean mEnterAnimationComplete = false; boolean mTouchExplorationEnabled; boolean mScreenPinningEnabled; @@ -537,17 +538,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (tv == null) { tv = mViewPool.pickUpViewFromPool(task, task); if (task.isFreeformTask()) { - tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE, + tv.updateViewPropertiesToTaskTransform(transform, AnimationProps.IMMEDIATE, mRequestUpdateClippingListener); } else { if (Float.compare(transform.p, 0f) <= 0) { tv.updateViewPropertiesToTaskTransform( mLayoutAlgorithm.getBackOfStackTransform(), - TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener); + AnimationProps.IMMEDIATE, mRequestUpdateClippingListener); } else { tv.updateViewPropertiesToTaskTransform( mLayoutAlgorithm.getFrontOfStackTransform(), - TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener); + AnimationProps.IMMEDIATE, mRequestUpdateClippingListener); } } } else { @@ -580,9 +581,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * animations that are current running on those task views, and will ensure that the children * {@link TaskView}s will match the set of visible tasks in the stack. * - * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>) + * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>) */ - void relayoutTaskViews(TaskViewAnimation animation) { + void relayoutTaskViews(AnimationProps animation) { relayoutTaskViews(animation, mIgnoreTasks); } @@ -594,7 +595,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * * @param ignoreTasksSet the set of tasks to ignore in the relayout */ - void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) { + void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet) { // If we had a deferred animation, cancel that mDeferredTaskViewLayoutAnimation = null; @@ -623,17 +624,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. */ - void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) { + void relayoutTaskViewsOnNextFrame(AnimationProps animation) { mDeferredTaskViewLayoutAnimation = animation; invalidate(); } /** * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a - * given set of {@link TaskViewAnimation} properties. + * given set of {@link AnimationProps} properties. */ public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, - TaskViewAnimation animation) { + AnimationProps animation) { taskView.updateViewPropertiesToTaskTransform(transform, animation, mRequestUpdateClippingListener); } @@ -1137,6 +1138,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mInMeasureLayout = true; int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); @@ -1159,22 +1161,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mTmpTaskViews.addAll(mViewPool.getViews()); int taskViewCount = mTmpTaskViews.size(); for (int i = 0; i < taskViewCount; i++) { - TaskView tv = mTmpTaskViews.get(i); - if (tv.getBackground() != null) { - tv.getBackground().getPadding(mTmpRect); - } else { - mTmpRect.setEmpty(); - } - tv.measure( - MeasureSpec.makeMeasureSpec( - mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, - MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec( - mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, - MeasureSpec.EXACTLY)); + measureTaskView(mTmpTaskViews.get(i)); } setMeasuredDimension(width, height); + mInMeasureLayout = false; + } + + /** + * Measures a TaskView. + */ + private void measureTaskView(TaskView tv) { + if (tv.getBackground() != null) { + tv.getBackground().getPadding(mTmpRect); + } else { + mTmpRect.setEmpty(); + } + tv.measure( + MeasureSpec.makeMeasureSpec( + mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec( + mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom, + MeasureSpec.EXACTLY)); } /** @@ -1190,15 +1199,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mTmpTaskViews.addAll(mViewPool.getViews()); int taskViewCount = mTmpTaskViews.size(); for (int i = 0; i < taskViewCount; i++) { - TaskView tv = mTmpTaskViews.get(i); - if (tv.getBackground() != null) { - tv.getBackground().getPadding(mTmpRect); - } else { - mTmpRect.setEmpty(); - } - Rect taskRect = mLayoutAlgorithm.mTaskRect; - tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top, - taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom); + layoutTaskView(mTmpTaskViews.get(i)); } if (changed) { @@ -1207,29 +1208,41 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } // Relayout all of the task views including the ignored ones - relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET); + relayoutTaskViews(AnimationProps.IMMEDIATE, EMPTY_TASK_SET); clipTaskViews(); if (mAwaitingFirstLayout || !mEnterAnimationComplete) { mAwaitingFirstLayout = false; onFirstLayout(); - return; } } + /** + * Lays out a TaskView. + */ + private void layoutTaskView(TaskView tv) { + if (tv.getBackground() != null) { + tv.getBackground().getPadding(mTmpRect); + } else { + mTmpRect.setEmpty(); + } + Rect taskRect = mLayoutAlgorithm.mTaskRect; + tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top, + taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom); + } + /** Handler for the first layout. */ void onFirstLayout() { // Setup the view for the enter animation mAnimationHelper.prepareForEnterAnimation(); // Animate in the freeform workspace - animateFreeformWorkspaceBackgroundAlpha( - mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150, - Interpolators.FAST_OUT_SLOW_IN); + int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha; + animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150, + Interpolators.FAST_OUT_SLOW_IN)); // Set the task focused state without requesting view focus, and leave the focus animations // until after the enter-animation - Task launchTask = mStack.getLaunchTarget(); RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount()); @@ -1274,7 +1287,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } @Override - protected void dispatchDraw(Canvas canvas) { + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + // Draw the freeform workspace background SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.hasFreeformWorkspaceSupport()) { @@ -1282,8 +1297,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mFreeformWorkspaceBackground.draw(canvas); } } - - super.dispatchDraw(canvas); } @Override @@ -1318,7 +1331,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal updateLayoutAlgorithm(true /* boundScroll */); // Animate all the tasks into place - relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN)); } @@ -1327,7 +1340,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ @Override public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, - Task newFrontMostTask, TaskViewAnimation animation) { + Task newFrontMostTask, AnimationProps animation) { if (mFocusedTask == removedTask) { resetFocusedTask(removedTask); } @@ -1364,7 +1377,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, - TaskViewAnimation animation) { + AnimationProps animation) { // To be implemented } @@ -1404,7 +1417,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Add/attach the view to the hierarchy if (isNewView) { - addView(tv, insertIndex); + if (mInMeasureLayout) { + // If we are measuring the layout, then just add the view normally as it will be + // laid out during the layout pass + addView(tv, insertIndex); + } else { + // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout + // pass, and we should layout the new child ourselves + ViewGroup.LayoutParams params = tv.getLayoutParams(); + if (params == null) { + params = generateDefaultLayoutParams(); + } + addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */); + measureTaskView(tv); + layoutTaskView(tv); + } } else { attachViewToParent(tv, insertIndex, tv.getLayoutParams()); } @@ -1463,14 +1490,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onFocusStateChanged(float prevFocusState, float curFocusState) { if (mDeferredTaskViewLayoutAnimation == null) { mUIDozeTrigger.poke(); - relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE); + relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE); } } /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @Override - public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) { + public void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation) { mUIDozeTrigger.poke(); if (animation != null) { relayoutTaskViewsOnNextFrame(animation); @@ -1506,7 +1533,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.dismissTask(); } else { // Otherwise, remove the task from the stack immediately - mStack.removeTask(t, TaskViewAnimation.IMMEDIATE); + mStack.removeTask(t, AnimationProps.IMMEDIATE); } } } @@ -1531,10 +1558,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); // Dismiss the freeform workspace background - int taskViewExitToHomeDuration = getResources().getInteger( - R.integer.recents_task_exit_to_home_duration); - animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration, - Interpolators.FAST_OUT_SLOW_IN); + int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION; + animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration, + Interpolators.FAST_OUT_SLOW_IN)); } public final void onBusEvent(DismissFocusedTaskViewEvent event) { @@ -1587,6 +1613,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(DragStartEvent event) { + // Ensure that the drag task is not animated + addIgnoreTask(event.task); + if (event.task.isFreeformTask()) { // Animate to the front of the stack mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null); @@ -1599,7 +1628,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mTmpTransform.scale = finalScale; mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; updateTaskViewToTransform(event.taskView, mTmpTransform, - new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); + new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); } public final void onBusEvent(DragStartInitializeDropTargetsEvent event) { @@ -1611,7 +1640,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(DragDropTargetChangedEvent event) { - TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN); + AnimationProps animation = new AnimationProps(250, Interpolators.FAST_OUT_SLOW_IN); if (event.dropTarget instanceof TaskStack.DockState) { // Calculate the new task stack bounds that matches the window size that Recents will // have after the drop @@ -1683,10 +1712,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), mTmpTransform, null); event.getAnimationTrigger().increment(); - relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN)); updateTaskViewToTransform(event.taskView, mTmpTransform, - new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN, + new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN, event.getAnimationTrigger().decrementOnAnimationEnd())); removeIgnoreTask(event.task); } @@ -1797,15 +1826,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal R.string.accessibility_recents_item_dismissed, task.title)); // Remove the task from the stack - mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN)); } /** * Starts an alpha animation on the freeform workspace background. */ - private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, int duration, - Interpolator interpolator) { + private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, + AnimationProps animation) { if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) { return; } @@ -1813,8 +1842,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator); mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground, Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha); - mFreeformWorkspaceBackgroundAnimator.setDuration(duration); - mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator); + mFreeformWorkspaceBackgroundAnimator.setStartDelay( + animation.getDuration(AnimationProps.ALPHA)); + mFreeformWorkspaceBackgroundAnimator.setDuration( + animation.getDuration(AnimationProps.ALPHA)); + mFreeformWorkspaceBackgroundAnimator.setInterpolator( + animation.getInterpolator(AnimationProps.ALPHA)); mFreeformWorkspaceBackgroundAnimator.start(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index ced5d4b500f4..c641d75c7bc8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -38,7 +38,7 @@ public class TaskStackViewScroller { private static final boolean DEBUG = false; public interface TaskStackViewScrollerCallbacks { - void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation); + void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation); } /** @@ -93,14 +93,14 @@ public class TaskStackViewScroller { * Sets the current stack scroll immediately. */ public void setStackScroll(float s) { - setStackScroll(s, TaskViewAnimation.IMMEDIATE); + setStackScroll(s, AnimationProps.IMMEDIATE); } /** * Sets the current stack scroll, but indicates to the callback the preferred animation to * update to this new scroll. */ - public void setStackScroll(float s, TaskViewAnimation animation) { + public void setStackScroll(float s, AnimationProps animation) { float prevStackScroll = mStackScrollP; mStackScrollP = s; if (mCb != null) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index da99956d2a9c..b8b506800488 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -546,7 +546,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mTmpTransform.translationZ = fromTransform.translationZ + (toTransform.translationZ - fromTransform.translationZ) * dismissFraction; - mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE); + mSv.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 853f8688767f..703005f00a49 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -29,7 +29,6 @@ import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; -import android.provider.Settings; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.IntProperty; @@ -243,9 +242,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks } void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, - TaskViewAnimation toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) { + AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) { RecentsConfiguration config = Recents.getConfiguration(); - Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation); + cancelTransformAnimation(); // Compose the animations for the transform mTmpAnimators.clear(); @@ -255,8 +254,8 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks setTaskProgress(toTransform.p); } // Manually call back to the animator listener and update callback - if (toAnimation.listener != null) { - toAnimation.listener.onAnimationEnd(null); + if (toAnimation.getListener() != null) { + toAnimation.getListener().onAnimationEnd(null); } if (updateCallback != null) { updateCallback.onAnimationUpdate(null); @@ -280,7 +279,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks /** Resets this view's properties */ void resetViewProperties() { - Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation); + cancelTransformAnimation(); setDim(0); setVisibility(View.VISIBLE); getViewBounds().reset(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java deleted file mode 100644 index 5455042241bc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2016 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.systemui.recents.views; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.view.animation.Interpolator; - -import com.android.systemui.Interpolators; - -import java.util.List; - -/** - * The animation properties to animate a {@link TaskView} to a given {@link TaskViewTransform}. - */ -public class TaskViewAnimation { - - public static final TaskViewAnimation IMMEDIATE = new TaskViewAnimation(0, - Interpolators.LINEAR); - - public final int startDelay; - public final int duration; - public final Interpolator interpolator; - public final Animator.AnimatorListener listener; - - public TaskViewAnimation(int duration, Interpolator interpolator) { - this(0 /* startDelay */, duration, interpolator, null); - } - - public TaskViewAnimation(int duration, Interpolator interpolator, - Animator.AnimatorListener listener) { - this(0 /* startDelay */, duration, interpolator, listener); - } - - public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator) { - this(startDelay, duration, interpolator, null); - } - - public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator, - Animator.AnimatorListener listener) { - this.startDelay = startDelay; - this.duration = duration; - this.interpolator = interpolator; - this.listener = listener; - } - - /** - * Creates a new {@link AnimatorSet} that will animate the given animators with the current - * animation properties. - */ - public AnimatorSet createAnimator(List<Animator> animators) { - AnimatorSet anim = new AnimatorSet(); - anim.setStartDelay(startDelay); - anim.setDuration(duration); - anim.setInterpolator(interpolator); - if (listener != null) { - anim.addListener(listener); - } - anim.playTogether(animators); - return anim; - } - - /** - * Returns whether this animation has any duration. - */ - public boolean isImmediate() { - return duration <= 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 7cde46397a81..c91a833e943a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -274,8 +274,8 @@ public class TaskViewHeader extends FrameLayout } @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); + public void onDrawForeground(Canvas canvas) { + super.onDrawForeground(canvas); // Draw the dim layer with the rounded corners canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius, diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 85b7c82112a4..32878b0afcb7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -154,13 +154,13 @@ public class TaskViewTransform { * Applies this transform to a view. */ public void applyToTaskView(TaskView v, ArrayList<Animator> animators, - TaskViewAnimation taskAnimation, boolean allowShadows) { + AnimationProps animation, boolean allowShadows) { // Return early if not visible if (!visible) { return; } - if (taskAnimation.isImmediate()) { + if (animation.isImmediate()) { if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) { v.setTranslationZ(translationZ); } @@ -177,23 +177,27 @@ public class TaskViewTransform { } } else { if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) { - animators.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, v.getTranslationZ(), - translationZ)); + ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, + v.getTranslationZ(), translationZ); + animators.add(animation.apply(AnimationProps.TRANSLATION_Z, anim)); } if (hasScaleChangedFrom(v.getScaleX())) { - animators.add(ObjectAnimator.ofPropertyValuesHolder(v, + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v, PropertyValuesHolder.ofFloat(View.SCALE_X, v.getScaleX(), scale), - PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale))); + PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale)); + animators.add(animation.apply(AnimationProps.SCALE, anim)); } if (hasAlphaChangedFrom(v.getAlpha())) { - animators.add(ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha)); + ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha); + animators.add(animation.apply(AnimationProps.ALPHA, anim)); } if (hasRectChangedFrom(v)) { - animators.add(ObjectAnimator.ofPropertyValuesHolder(v, + ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v, PropertyValuesHolder.ofInt(LEFT, v.getLeft(), (int) rect.left), PropertyValuesHolder.ofInt(TOP, v.getTop(), (int) rect.top), PropertyValuesHolder.ofInt(RIGHT, v.getRight(), (int) rect.right), - PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom))); + PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom)); + animators.add(animation.apply(AnimationProps.BOUNDS, anim)); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 00b9888d69b0..76e522e336ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -197,9 +197,13 @@ public class NotificationContentView extends FrameLayout { if (expandedSize != collapsedSize) { int paddingEnd = contractedHeader.getMeasuredWidth() - expandedSize; contractedHeader.setPadding( - isLayoutRtl() ? paddingEnd : contractedHeader.getPaddingLeft(), + contractedHeader.isLayoutRtl() + ? paddingEnd + : contractedHeader.getPaddingLeft(), contractedHeader.getPaddingTop(), - isLayoutRtl() ? contractedHeader.getPaddingLeft() : paddingEnd, + contractedHeader.isLayoutRtl() + ? contractedHeader.getPaddingLeft() + : paddingEnd, contractedHeader.getPaddingBottom()); contractedHeader.setShowWorkBadgeAtEnd(true); return true; @@ -208,9 +212,13 @@ public class NotificationContentView extends FrameLayout { int paddingEnd = mNotificationContentMarginEnd; if (contractedHeader.getPaddingEnd() != paddingEnd) { contractedHeader.setPadding( - isLayoutRtl() ? paddingEnd : contractedHeader.getPaddingLeft(), + contractedHeader.isLayoutRtl() + ? paddingEnd + : contractedHeader.getPaddingLeft(), contractedHeader.getPaddingTop(), - isLayoutRtl() ? contractedHeader.getPaddingLeft() : paddingEnd, + contractedHeader.isLayoutRtl() + ? contractedHeader.getPaddingLeft() + : paddingEnd, contractedHeader.getPaddingBottom()); contractedHeader.setShowWorkBadgeAtEnd(false); return true; diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java index 6febe5fe8a4b..3e47d8571fad 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java @@ -32,11 +32,14 @@ import android.os.Handler; import android.os.RemoteException; import android.util.Log; +import com.android.systemui.Prefs; + import java.util.ArrayList; import java.util.List; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN; /** * Manages the picture-in-picture (PIP) UI and states. @@ -44,6 +47,7 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID; public class PipManager { private static final String TAG = "PipManager"; private static final boolean DEBUG = false; + private static final boolean DEBUG_FORCE_ONBOARDING = false; private static PipManager sPipManager; @@ -65,6 +69,7 @@ public class PipManager { private Rect mMenuModePipBound; private boolean mInitialized; private int mPipTaskId = TASK_ID_NO_PIP; + private boolean mOnboardingShown; private final Runnable mOnActivityPinnedRunnable = new Runnable() { @Override @@ -83,6 +88,7 @@ public class PipManager { if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo); mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1]; showPipOverlay(false); + launchPipOnboardingActivityIfNeeded(); } }; private final Runnable mOnTaskStackChanged = new Runnable() { @@ -145,6 +151,8 @@ public class PipManager { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED); mContext.registerReceiver(mBroadcastReceiver, intentFilter); + mOnboardingShown = Prefs.getBoolean( + mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false); } /** @@ -169,7 +177,7 @@ public class PipManager { } /** - * Closes PIP (PIPped activity and PIP system UI). + * Closes PIP (PIPed activity and PIP system UI). */ public void closePip() { mState = STATE_NO_PIP; @@ -194,7 +202,7 @@ public class PipManager { } /** - * Moves the PIPped activity to the fullscreen and closes PIP system UI. + * Moves the PIPed activity to the fullscreen and closes PIP system UI. */ public void movePipToFullscreen() { mState = STATE_NO_PIP; @@ -260,6 +268,17 @@ public class PipManager { mListeners.remove(listener); } + private void launchPipOnboardingActivityIfNeeded() { + if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) { + mOnboardingShown = true; + Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true); + + Intent intent = new Intent(mContext, PipOnboardingActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + } + private boolean hasPipTasks() { try { StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); @@ -334,7 +353,7 @@ public class PipManager { */ public interface Listener { /** - * Invoked when a PIPped activity is closed. + * Invoked when a PIPed activity is closed. */ void onPipActivityClosed(); /** @@ -342,7 +361,7 @@ public class PipManager { */ void onShowPipMenu(); /** - * Invoked when the PIPped activity is returned back to the fullscreen. + * Invoked when the PIPed activity is returned back to the fullscreen. */ void onMoveToFullscreen(); } diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java new file mode 100644 index 000000000000..a0b913ab9b2c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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.systemui.tv.pip; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import com.android.systemui.R; + +/** + * Activity to show an overlay on top of PIP activity to show how to pop up PIP menu. + */ +public class PipOnboardingActivity extends Activity implements PipManager.Listener { + private final PipManager mPipManager = PipManager.getInstance(); + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.tv_pip_onboarding); + mPipManager.addListener(this); + findViewById(R.id.close).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mPipManager.removeListener(this); + } + + @Override + public void onPipActivityClosed() { + finish(); + } + + @Override + public void onShowPipMenu() { + finish(); + } + + @Override + public void onMoveToFullscreen() { + finish(); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java index e31080150280..912e8e2eb246 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java @@ -27,6 +27,8 @@ import android.gesture.Prediction; import android.util.Slog; import android.view.GestureDetector; import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; import com.android.internal.R; @@ -46,7 +48,9 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen public interface Listener { public void onDoubleTapAndHold(MotionEvent event, int policyFlags); public boolean onDoubleTap(MotionEvent event, int policyFlags); - public boolean onGesture(int gestureId); + public boolean onGestureCompleted(int gestureId); + public void onGestureStarted(); + public void onGestureCancelled(MotionEvent event, int policyFlags); } private final Listener mListener; @@ -64,6 +68,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen // Indicates that motion events are being collected to match a gesture. private boolean mRecognizingGesture; + // Indicates that we've collected enough data to be sure it could be a + // gesture. + private boolean mGestureConfirmed; + // Indicates that motion events from the second pointer are being checked // for a double tap. private boolean mSecondFingerDoubleTap; @@ -81,18 +89,41 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen // The Y of the previous event. private float mPreviousY; + // The X of the down event. + private float mBaseX; + + // The Y of the down event. + private float mBaseY; + + // Slop between the first and second tap to be a double tap. + private final int mDoubleTapSlop; + + // The scaled velocity above which we detect gestures. + private final int mScaledGestureDetectionVelocity; + // Buffer for storing points for gesture detection. private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100); + // Helper to track gesture velocity. + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + // The minimal delta between moves to add a gesture point. private static final int TOUCH_TOLERANCE = 3; // The minimal score for accepting a predicted gesture. private static final float MIN_PREDICTION_SCORE = 2.0f; + // The velocity above which we detect gestures. Expressed in DIPs/Second. + private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000; + + // Constant used to calculate velocity in seconds. + private static final int VELOCITY_UNITS_SECONDS = 1000; + AccessibilityGestureDetector(Context context, Listener listener) { mListener = listener; + mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + mGestureDetector = new GestureDetector(context, this); mGestureDetector.setOnDoubleTapListener(this); @@ -100,9 +131,14 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */); mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE); mGestureLibrary.load(); + + final float density = context.getResources().getDisplayMetrics().density; + mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); } public boolean onMotionEvent(MotionEvent event, int policyFlags) { + mVelocityTracker.addMovement(event); + final float x = event.getX(); final float y = event.getY(); @@ -112,14 +148,48 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen mDoubleTapDetected = false; mSecondFingerDoubleTap = false; mRecognizingGesture = true; + mGestureConfirmed = false; + mBaseX = x; + mBaseY = y; mPreviousX = x; mPreviousY = y; mStrokeBuffer.clear(); mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + mVelocityTracker.clear(); + mVelocityTracker.addMovement(event); break; case MotionEvent.ACTION_MOVE: if (mRecognizingGesture) { + if (!mGestureConfirmed) { + mVelocityTracker.addMovement(event); + // It is *important* to use the distance traveled by the pointers + // on the screen which may or may not be magnified. + final float deltaX = mBaseX - event.getX(0); + final float deltaY = mBaseY - event.getY(0); + final double moveDelta = Math.hypot(deltaX, deltaY); + // The user has moved enough for us to decide. + if (moveDelta > mDoubleTapSlop) { + // Check whether the user is performing a gesture. We + // detect gestures if the pointer is moving above a + // given velocity. + mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS); + final float maxAbsVelocity = Math.max( + Math.abs(mVelocityTracker.getXVelocity(0)), + Math.abs(mVelocityTracker.getYVelocity(0))); + if (maxAbsVelocity > mScaledGestureDetectionVelocity) { + // We have to perform gesture detection, so + // notify the listener. + mGestureConfirmed = true; + mListener.onGestureStarted(); + } else { + // This won't match any gesture, so notify the + // listener. + cancelGesture(); + mListener.onGestureCancelled(event, policyFlags); + } + } + } final float dX = Math.abs(x - mPreviousX); final float dY = Math.abs(y - mPreviousY); if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { @@ -134,12 +204,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen if (maybeFinishDoubleTap(event, policyFlags)) { return true; } - if (mRecognizingGesture) { + if (mGestureConfirmed) { mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); - if (recognizeGesture()) { - return true; + if (!recognizeGesture()) { + mListener.onGestureCancelled(event, policyFlags); } + return true; } break; @@ -167,6 +238,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen return true; } break; + + case MotionEvent.ACTION_CANCEL: + clear(); + break; } // If we're detecting taps on the second finger, map events from the @@ -194,18 +269,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen mDoubleTapDetected = false; mSecondFingerDoubleTap = false; cancelGesture(); - mStrokeBuffer.clear(); + mVelocityTracker.clear(); } public boolean firstTapDetected() { return mFirstTapDetected; } - public void cancelGesture() { - mRecognizingGesture = false; - mStrokeBuffer.clear(); - } - @Override public void onLongPress(MotionEvent e) { maybeSendLongPress(e, mPolicyFlags); @@ -251,6 +321,12 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen return mListener.onDoubleTap(event, policyFlags); } + private void cancelGesture() { + mRecognizingGesture = false; + mGestureConfirmed = false; + mStrokeBuffer.clear(); + } + private boolean recognizeGesture() { Gesture gesture = new Gesture(); gesture.addStroke(new GestureStroke(mStrokeBuffer)); @@ -265,7 +341,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen } try { final int gestureId = Integer.parseInt(bestPrediction.name); - if (mListener.onGesture(gestureId)) { + if (mListener.onGestureCompleted(gestureId)) { return true; } } catch (NumberFormatException nfe) { diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index ca30349867de..9e6cd002e889 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -25,7 +25,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; -import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; @@ -87,9 +86,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe // Invalid pointer ID. private static final int INVALID_POINTER_ID = -1; - // The velocity above which we detect gestures. - private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000; - // The minimal distance before we take the middle of the distance between // the two dragging pointers as opposed to use the location of the primary one. private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200; @@ -134,15 +130,9 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe // the two dragging pointers as opposed to use the location of the primary one. private final int mScaledMinPointerDistanceToUseMiddleLocation; - // The scaled velocity above which we detect gestures. - private final int mScaledGestureDetectionVelocity; - // The handler to which to delegate events. private EventStreamTransformation mNext; - // Helper to track gesture velocity. - private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - // Helper class to track received pointers. private final ReceivedPointerTracker mReceivedPointerTracker; @@ -200,7 +190,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe final float density = context.getResources().getDisplayMetrics().density; mScaledMinPointerDistanceToUseMiddleLocation = (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density); - mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); } @Override @@ -289,11 +278,19 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe mReceivedPointerTracker.onMotionEvent(rawEvent); - if (mGestureDetector.onMotionEvent(event, policyFlags)) { + // The motion detector is interested in the movements in physical space, + // so it uses the rawEvent to ignore magnification and other + // transformations. + if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) { // Event was handled by the gesture detector. return; } + if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { + clear(event, policyFlags); + return; + } + switch(mCurrentState) { case STATE_TOUCH_EXPLORING: { handleMotionEventStateTouchExploring(event, rawEvent, policyFlags); @@ -305,7 +302,7 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe handleMotionEventStateDelegating(event, policyFlags); } break; case STATE_GESTURE_DETECTING: { - handleMotionEventGestureDetecting(rawEvent, policyFlags); + // Already handled. } break; default: throw new IllegalStateException("Illegal state: " + mCurrentState); @@ -440,26 +437,50 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } @Override - public boolean onGesture(int gestureId) { + public boolean onGestureCompleted(int gestureId) { if (mCurrentState != STATE_GESTURE_DETECTING) { return false; } - mAms.onTouchInteractionEnd(); - - // Announce the end of the gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Announce the end of a the touch interaction. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + endGestureDetection(); mAms.onGesture(gestureId); - mExitGestureDetectionModeDelayed.cancel(); - mCurrentState = STATE_TOUCH_EXPLORING; - return true; } + @Override + public void onGestureStarted() { + // We have to perform gesture detection, so + // clear the current state and try to detect. + mCurrentState = STATE_GESTURE_DETECTING; + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mExitGestureDetectionModeDelayed.post(); + // Send accessibility event to announce the start + // of gesture recognition. + sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START); + } + + @Override + public void onGestureCancelled(MotionEvent event, int policyFlags) { + if (mCurrentState == STATE_GESTURE_DETECTING) { + endGestureDetection(); + } else if (mCurrentState == STATE_TOUCH_EXPLORING) { + final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); + final int pointerIdBits = (1 << pointerId); + + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); + + // We have just decided that the user is touch, + // exploring so start sending events. + mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); + mSendHoverExitDelayed.cancel(); + sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); + } + } + /** * Handles a motion event in touch exploring state. * @@ -471,8 +492,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe int policyFlags) { ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - mVelocityTracker.addMovement(rawEvent); - switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mAms.onTouchInteractionStart(); @@ -525,46 +544,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe if (mSendHoverEnterAndMoveDelayed.isPending()) { // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event); - - // It is *important* to use the distance traveled by the pointers - // on the screen which may or may not be magnified. - final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) - - rawEvent.getX(pointerIndex); - final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) - - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - // The user has moved enough for us to decide. - if (moveDelta > mDoubleTapSlop) { - // Check whether the user is performing a gesture. We - // detect gestures if the pointer is moving above a - // given velocity. - mVelocityTracker.computeCurrentVelocity(1000); - final float maxAbsVelocity = Math.max( - Math.abs(mVelocityTracker.getXVelocity(pointerId)), - Math.abs(mVelocityTracker.getYVelocity(pointerId))); - if (maxAbsVelocity > mScaledGestureDetectionVelocity) { - // We have to perform gesture detection, so - // clear the current state and try to detect. - mCurrentState = STATE_GESTURE_DETECTING; - mVelocityTracker.clear(); - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mExitGestureDetectionModeDelayed.post(); - // Send accessibility event to announce the start - // of gesture recognition. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_GESTURE_DETECTION_START); - } else { - // We have just decided that the user is touch, - // exploring so start sending events. - mGestureDetector.cancelGesture(); - mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); - mSendHoverExitDelayed.cancel(); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, - pointerIdBits, policyFlags); - } - break; - } } else { if (mTouchExplorationInProgress) { sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); @@ -602,11 +581,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } } - // We know that a new state transition is to happen and the - // new state will not be gesture recognition, so cancel - // the gesture. - mGestureDetector.cancelGesture(); - if (isDraggingGesture(event)) { // Two pointers moving in the same direction within // a given distance perform a drag. @@ -620,7 +594,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe mCurrentState = STATE_DELEGATING; sendDownForAllNotInjectedPointers(event, policyFlags); } - mVelocityTracker.clear(); } break; default: { // More than one pointer so the user is not touch exploring @@ -639,7 +612,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe // More than two pointers are delegated to the view hierarchy. mCurrentState = STATE_DELEGATING; sendDownForAllNotInjectedPointers(event, policyFlags); - mVelocityTracker.clear(); } } } break; @@ -648,8 +620,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe final int pointerId = event.getPointerId(event.getActionIndex()); final int pointerIdBits = (1 << pointerId); - mVelocityTracker.clear(); - if (mSendHoverEnterAndMoveDelayed.isPending()) { // If we have not delivered the enter schedule an exit. mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); @@ -663,9 +633,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; } } @@ -756,9 +723,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } mCurrentState = STATE_TOUCH_EXPLORING; } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; } } @@ -795,9 +759,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe mCurrentState = STATE_TOUCH_EXPLORING; } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; default: { // Deliver the event. sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); @@ -805,22 +766,16 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } } - private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); - // Announce the end of the gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Announce the end of a the touch interaction. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + private void endGestureDetection() { + mAms.onTouchInteractionEnd(); - mExitGestureDetectionModeDelayed.cancel(); - mCurrentState = STATE_TOUCH_EXPLORING; - } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; - } + // Announce the end of the gesture recognition. + sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); + // Announce the end of a the touch interaction. + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + + mExitGestureDetectionModeDelayed.cancel(); + mCurrentState = STATE_TOUCH_EXPLORING; } /** diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index d0cd5365db15..81607a98c3d0 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -200,7 +200,7 @@ public class LockSettingsService extends ILockSettings.Stub { PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new Notification.Builder(mContext) - .setSmallIcon(com.android.internal.R.drawable.ic_secure) + .setSmallIcon(com.android.internal.R.drawable.ic_user_secure) .setWhen(0) .setOngoing(true) .setTicker(title) diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 4a186a620cc1..3ce44526d17b 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -2817,8 +2817,9 @@ class MountService extends IMountService.Stub public ParcelFileDescriptor mountAppFuse(final String name) throws RemoteException { try { final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); final NativeDaemonEvent event = - mConnector.execute("appfuse", "mount", uid, name); + mConnector.execute("appfuse", "mount", uid, pid, name); if (event.getFileDescriptors() == null) { throw new RemoteException("AppFuse FD from vold is null."); } @@ -2830,7 +2831,7 @@ class MountService extends IMountService.Stub public void onClose(IOException e) { try { final NativeDaemonEvent event = mConnector.execute( - "appfuse", "unmount", uid, name); + "appfuse", "unmount", uid, pid, name); } catch (NativeDaemonConnectorException unmountException) { Log.e(TAG, "Failed to unmount appfuse."); } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 143015f0f6ab..2683be66b347 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4266,6 +4266,21 @@ public class AccountManagerService } } + private boolean isPermitted(String opPackageName, int callingUid, String... permissions) { + for (String perm : permissions) { + if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, " caller uid " + callingUid + " has " + perm); + } + final int opCode = AppOpsManager.permissionToOpCode(perm); + if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp( + opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) { + return true; + } + } + } + return false; + } private int handleIncomingUser(int userId) { try { @@ -4340,53 +4355,12 @@ public class AccountManagerService private List<String> getTypesVisibleToCaller(int callingUid, int userId, String opPackageName) { - List<String> permissionsToCheck = new ArrayList<String>(2); - permissionsToCheck.add(Manifest.permission.GET_ACCOUNTS_PRIVILEGED); - long id = Binder.clearCallingIdentity(); - try { - ApplicationInfo appInfo = mPackageManager.getApplicationInfo( - opPackageName, 0 /* flags */); - /* - * At or before SDK 23, clients discover all the accounts in their - * user profile (via AccountManager.getAccounts(...)) by declaring - * the GET_ACCOUNTS permission. - * - * After SDK 23 the GET_ACCOUNTS permission is deprecated. Instead - * apps will be able to retrieve those accounts managed by - * authenticators sharing a package signature without any special - * permissions. The only clients able to discover all the accounts - * on the device will be those with the GET_ACCOUNTS_PRVILEGED - * system permission. - */ - if (23 >= appInfo.targetSdkVersion) { - permissionsToCheck.add(Manifest.permission.GET_ACCOUNTS); - } - } catch (NameNotFoundException e) { - // No application associated with the specified package. - Log.w(TAG, "No application associated with package: " + opPackageName); - } finally { - Binder.restoreCallingIdentity(id); - } - boolean isPermitted = isPermitted(opPackageName, callingUid, permissionsToCheck); + boolean isPermitted = + isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS, + Manifest.permission.GET_ACCOUNTS_PRIVILEGED); return getTypesForCaller(callingUid, userId, isPermitted); } - private boolean isPermitted(String opPackageName, int callingUid, List<String> permissions) { - for (String perm : permissions) { - if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, " caller uid " + callingUid + " has " + perm); - } - final int opCode = AppOpsManager.permissionToOpCode(perm); - if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp( - opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) { - return true; - } - } - } - return false; - } - private List<String> getTypesManagedByCaller(int callingUid, int userId) { return getTypesForCaller(callingUid, userId, false); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ba5bb396c5ca..54c4ced18e47 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18080,7 +18080,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app == TOP_APP) { // The last app on the list is the foreground app. adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; + schedGroup = Process.THREAD_GROUP_TOP_APP; app.adjType = "top-activity"; foregroundActivities = true; procState = PROCESS_STATE_CUR_TOP; diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 3ad6b8329e87..914776ea488a 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -584,13 +584,16 @@ final class ActivityRecord { haveState = true; if (aInfo != null) { + // If the class name in the intent doesn't match that of the target, this is + // probably an alias. We have to create a new ComponentName object to keep track + // of the real activity name, so that FLAG_ACTIVITY_CLEAR_TOP is handled properly. if (aInfo.targetActivity == null - || aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE - || aInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { + || (aInfo.targetActivity.equals(_intent.getComponent().getClassName()) + && (aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE + || aInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP))) { realActivity = _intent.getComponent(); } else { - realActivity = new ComponentName(aInfo.packageName, - aInfo.targetActivity); + realActivity = new ComponentName(aInfo.packageName, aInfo.targetActivity); } taskAffinity = aInfo.taskAffinity; stateNotNeeded = (aInfo.flags& diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 98eebeaca953..b360b897d1ee 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1416,9 +1416,7 @@ class ActivityStarter { if (mLaunchBounds != null) { final int stackId = mTargetStack.mStackId; if (StackId.resizeStackWithLaunchBounds(stackId)) { - mSupervisor.resizeStackLocked(stackId, mLaunchBounds, - null /* tempTaskBounds */, null /* tempTaskInsetBounds */, - !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */); + mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE); } else { mStartActivity.task.updateOverrideConfiguration(mLaunchBounds); } @@ -1506,9 +1504,7 @@ class ActivityStarter { stackId = stack.mStackId; } if (StackId.resizeStackWithLaunchBounds(stackId)) { - mSupervisor.resizeStackLocked(stackId, mLaunchBounds, - null /* tempTaskBounds */, null /* tempTaskInsetBounds */, - !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */); + mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE); } } mTargetStack = mInTask.stack; diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 91d4c9e36c1a..daf839a59e1b 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -600,9 +600,18 @@ public class SyncManager { null, null); } - Intent startServiceIntent = new Intent(mContext, SyncJobService.class); + // Set up the communication channel between the scheduled job and the sync manager. + // This is posted to the *main* looper intentionally, to defer calling startService() + // until after the lengthy primary boot sequence completes on that thread, to avoid + // spurious ANR triggering. + final Intent startServiceIntent = new Intent(mContext, SyncJobService.class); startServiceIntent.putExtra(SyncJobService.EXTRA_MESSENGER, new Messenger(mSyncHandler)); - mContext.startService(startServiceIntent); + new Handler(mContext.getMainLooper()).post(new Runnable() { + @Override + public void run() { + mContext.startService(startServiceIntent); + } + }); } private boolean isDeviceProvisioned() { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 5c7707968a46..544f25522bf3 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -255,6 +255,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6; private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7; private static final int MSG_SCREEN_ON_CHANGED = 8; + private static final int MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED = 9; private final Context mContext; private final IActivityManager mActivityManager; @@ -1864,6 +1865,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writePolicyLocked(); // TODO: call other update methods like updateNetworkRulesLocked? } + mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget(); } @Override @@ -1873,6 +1875,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { synchronized (mRulesLock) { removeRestrictBackgroundWhitelistedUidLocked(uid, true); } + mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget(); } private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean writePolicy) { @@ -2571,6 +2574,25 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } mListeners.finishBroadcast(); + final Intent intent = + new Intent(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + return true; + } + case MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED: { + final int uid = msg.arg1; + final PackageManager pm = mContext.getPackageManager(); + final String[] packages = pm.getPackagesForUid(uid); + final int userId = UserHandle.getUserId(uid); + for (String packageName : packages) { + final Intent intent = + new Intent(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED); + intent.setPackage(packageName); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + } + return true; } case MSG_ADVISE_PERSIST_THRESHOLD: { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java index 7b1accafc673..5830b0e8bfaa 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java @@ -20,6 +20,7 @@ import java.io.PrintWriter; import android.content.Intent; import android.net.INetworkPolicyManager; +import android.os.Binder; import android.os.RemoteException; import android.os.ShellCommand; @@ -86,7 +87,7 @@ public class NetworkPolicyManagerShellCommand extends ShellCommand { } switch(type) { case "restrict-background": - return getRestrictBackgroundWhitelist(); + return getRestrictBackground(); } pw.println("Error: unknown get type '" + type + "'"); return -1; @@ -101,7 +102,7 @@ public class NetworkPolicyManagerShellCommand extends ShellCommand { } switch(type) { case "restrict-background": - return setRestrictBackgroundWhitelist(); + return setRestrictBackground(); } pw.println("Error: unknown set type '" + type + "'"); return -1; @@ -169,19 +170,24 @@ public class NetworkPolicyManagerShellCommand extends ShellCommand { return 0; } - private int getRestrictBackgroundWhitelist() throws RemoteException { + private int getRestrictBackground() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); pw.print("Restrict background status: "); pw.println(mInterface.getRestrictBackground() ? "enabled" : "disabled"); return 0; } - private int setRestrictBackgroundWhitelist() throws RemoteException { + private int setRestrictBackground() throws RemoteException { final int enabled = getNextBooleanArg(); if (enabled < 0) { return enabled; } - mInterface.setRestrictBackground(enabled > 0); + final long token = Binder.clearCallingIdentity(); + try { + mInterface.setRestrictBackground(enabled > 0); + } finally { + Binder.restoreCallingIdentity(token); + } return 0; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ebdb1b242e66..078094cfef7a 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1265,6 +1265,12 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean hasBannedTopics(String pkg, int uid) { + checkCallerIsSystem(); + return mRankingHelper.hasBannedTopics(pkg, uid); + } + + @Override public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) { checkCallerIsSystem(); return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid)); diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 7f85e1f8123d..1a7e3550d8e3 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -37,4 +37,6 @@ public interface RankingConfig { int getImportance(String packageName, int uid, Notification.Topic topic); boolean doesAppUseTopics(String packageName, int uid); + + boolean hasBannedTopics(String packageName, int uid); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 827482ff2a4b..aa36e298bb45 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -389,6 +389,17 @@ public class RankingHelper implements RankingConfig { return topics; } + @Override + public boolean hasBannedTopics(String packageName, int uid) { + final Record r = getOrCreateRecord(packageName, uid); + for (Topic t : r.topics.values()) { + if (t.importance == Ranking.IMPORTANCE_NONE) { + return true; + } + } + return false; + } + /** * Gets priority. If a topic is given, returns the priority of that topic. Otherwise, the * priority of the app. diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 5d97afaf9793..dac89ec759f7 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -49,6 +49,8 @@ public final class Installer extends SystemService { public static final int DEXOPT_BOOTCOMPLETE = 1 << 4; /** Do not compile, only extract bytecode into an OAT file */ public static final int DEXOPT_EXTRACTONLY = 1 << 5; + /** This is an OTA update dexopt */ + public static final int DEXOPT_OTA = 1 << 6; /** @hide */ @IntDef(flag = true, value = { diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java new file mode 100644 index 000000000000..da62a2d6fc84 --- /dev/null +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2016 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.pm; + +import android.app.AppGlobals; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IOtaDexopt; +import android.content.pm.PackageParser; +import android.content.pm.PackageParser.Package; +import android.content.pm.ResolveInfo; +import android.os.Environment; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.storage.StorageManager; +import android.util.ArraySet; +import android.util.Log; + +import dalvik.system.DexFile; + +import java.io.File; +import java.io.FileDescriptor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import static com.android.server.pm.Installer.DEXOPT_OTA; + +/** + * A service for A/B OTA dexopting. + * + * {@hide} + */ +public class OtaDexoptService extends IOtaDexopt.Stub { + private final static String TAG = "OTADexopt"; + private final static boolean DEBUG_DEXOPT = true; + // Apps used in the last 7 days. + private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60; + + private final Context mContext; + private final PackageDexOptimizer mPackageDexOptimizer; + private final PackageManagerService mPackageManagerService; + + // TODO: Evaluate the need for WeakReferences here. + private List<PackageParser.Package> mDexoptPackages; + + public OtaDexoptService(Context context, PackageManagerService packageManagerService) { + this.mContext = context; + this.mPackageManagerService = packageManagerService; + + // Use the package manager install and install lock here for the OTA dex optimizer. + mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller, + packageManagerService.mInstallLock, context); + } + + public static OtaDexoptService main(Context context, + PackageManagerService packageManagerService) { + OtaDexoptService ota = new OtaDexoptService(context, packageManagerService); + ServiceManager.addService("otadexopt", ota); + + return ota; + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ResultReceiver resultReceiver) throws RemoteException { + (new OtaDexoptShellCommand(this)).exec( + this, in, out, err, args, resultReceiver); + } + + @Override + public synchronized void prepare() throws RemoteException { + if (mDexoptPackages != null) { + throw new IllegalStateException("already called prepare()"); + } + + mDexoptPackages = new LinkedList<>(); + + ArrayList<PackageParser.Package> pkgs; + synchronized (mPackageManagerService.mPackages) { + pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values()); + } + + // Sort apps by importance for dexopt ordering. Important apps are given more priority + // in case the device runs out of space. + + // Give priority to core apps. + for (PackageParser.Package pkg : pkgs) { + if (pkg.coreApp) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName); + } + mDexoptPackages.add(pkg); + } + } + pkgs.removeAll(mDexoptPackages); + + // Give priority to system apps that listen for pre boot complete. + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); + for (PackageParser.Package pkg : pkgs) { + if (pkgNames.contains(pkg.packageName)) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " + + pkg.packageName); + } + mDexoptPackages.add(pkg); + } + } + pkgs.removeAll(mDexoptPackages); + + // Filter out packages that aren't recently used, add all remaining apps. + // TODO: add a property to control this? + if (mPackageManagerService.isHistoricalPackageUsageAvailable()) { + filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000); + } + mDexoptPackages.addAll(pkgs); + + // Now go ahead and also add the libraries required for these packages. + // TODO: Think about interleaving things. + Set<PackageParser.Package> dependencies = new HashSet<>(); + for (PackageParser.Package p : mDexoptPackages) { + dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p)); + } + if (!dependencies.isEmpty()) { + dependencies.removeAll(mDexoptPackages); + } + mDexoptPackages.addAll(dependencies); + + if (DEBUG_DEXOPT) { + StringBuilder sb = new StringBuilder(); + for (PackageParser.Package pkg : mDexoptPackages) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(pkg.packageName); + } + Log.i(TAG, "Packages to be optimized: " + sb.toString()); + } + } + + @Override + public synchronized void cleanup() throws RemoteException { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Cleaning up OTA Dexopt state."); + } + mDexoptPackages = null; + } + + @Override + public synchronized boolean isDone() throws RemoteException { + if (mDexoptPackages == null) { + throw new IllegalStateException("done() called before prepare()"); + } + + return mDexoptPackages.isEmpty(); + } + + @Override + public synchronized void dexoptNextPackage() throws RemoteException { + if (mDexoptPackages == null) { + throw new IllegalStateException("dexoptNextPackage() called before prepare()"); + } + if (mDexoptPackages.isEmpty()) { + // Tolerate repeated calls. + return; + } + + PackageParser.Package nextPackage = mDexoptPackages.remove(0); + + if (DEBUG_DEXOPT) { + Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt."); + } + + // Check for low space. + // TODO: If apps are not installed in the internal /data partition, we should compare + // against that storage's free capacity. + File dataDir = Environment.getDataDirectory(); + long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir); + if (lowThreshold == 0) { + throw new IllegalStateException("Invalid low memory threshold"); + } + long usableSpace = dataDir.getUsableSpace(); + if (usableSpace < lowThreshold) { + Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " + + usableSpace); + return; + } + + mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */, + false /* extractOnly */); + } + + private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { + List<ResolveInfo> ris = null; + try { + ris = AppGlobals.getPackageManager().queryIntentReceivers( + intent, null, 0, userId); + } catch (RemoteException e) { + } + ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size()); + if (ris != null) { + for (ResolveInfo ri : ris) { + pkgNames.add(ri.activityInfo.packageName); + } + } + return pkgNames; + } + + private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs, + long dexOptLRUThresholdInMills) { + // Filter out packages that aren't recently used. + int total = pkgs.size(); + int skipped = 0; + long now = System.currentTimeMillis(); + for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) { + PackageParser.Package pkg = i.next(); + long then = pkg.mLastPackageUsageTimeInMills; + if (then + dexOptLRUThresholdInMills < now) { + if (DEBUG_DEXOPT) { + Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " + + ((then == 0) ? "never" : new Date(then))); + } + i.remove(); + skipped++; + } + } + if (DEBUG_DEXOPT) { + Log.i(TAG, "Skipped optimizing " + skipped + " of " + total); + } + } + + private static class OTADexoptPackageDexOptimizer extends + PackageDexOptimizer.ForcedUpdatePackageDexOptimizer { + + public OTADexoptPackageDexOptimizer(Installer installer, Object installLock, + Context context) { + super(installer, installLock, context, "*otadexopt*"); + } + + @Override + protected int adjustDexoptFlags(int dexoptFlags) { + // Add the OTA flag. + return dexoptFlags | DEXOPT_OTA; + } + + @Override + protected void recordSuccessfulDexopt(Package pkg, String instructionSet) { + // Never record the dexopt, as it's in the B partition. + } + + } +} diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java new file mode 100644 index 000000000000..ea9cf1766232 --- /dev/null +++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java @@ -0,0 +1,100 @@ +/* + * 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.pm; + +import android.content.pm.IOtaDexopt; +import android.os.RemoteException; +import android.os.ShellCommand; + +import java.io.PrintWriter; + +class OtaDexoptShellCommand extends ShellCommand { + final IOtaDexopt mInterface; + + OtaDexoptShellCommand(OtaDexoptService service) { + mInterface = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + + final PrintWriter pw = getOutPrintWriter(); + try { + switch(cmd) { + case "prepare": + return runOtaPrepare(); + case "cleanup": + return runOtaCleanup(); + case "done": + return runOtaDone(); + case "step": + return runOtaStep(); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + private int runOtaPrepare() throws RemoteException { + mInterface.prepare(); + getOutPrintWriter().println("Success"); + return 0; + } + + private int runOtaCleanup() throws RemoteException { + mInterface.cleanup(); + return 0; + } + + private int runOtaDone() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + if (mInterface.isDone()) { + pw.println("OTA complete."); + } else { + pw.println("OTA incomplete."); + } + return 0; + } + + private int runOtaStep() throws RemoteException { + mInterface.dexoptNextPackage(); + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("OTA Dexopt (ota) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(""); + pw.println(" prepare"); + pw.println(" Prepare an OTA dexopt pass, collecting all packages."); + pw.println(" done"); + pw.println(" Replies whether the OTA is complete or not."); + pw.println(" step"); + pw.println(" OTA dexopt the next package."); + pw.println(" cleanup"); + pw.println(" Clean up internal states. Ends an OTA session."); + } +} diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index fe0f1416371d..64af21313a1d 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.Package; import android.os.PowerManager; import android.os.UserHandle; import android.os.WorkSource; @@ -48,7 +49,7 @@ import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; /** * Helper class for running dexopt command on packages. */ -final class PackageDexOptimizer { +class PackageDexOptimizer { private static final String TAG = "PackageManager.DexOptimizer"; static final String OAT_DIR_NAME = "oat"; // TODO b/19550105 Remove error codes and use exceptions @@ -57,16 +58,28 @@ final class PackageDexOptimizer { static final int DEX_OPT_DEFERRED = 2; static final int DEX_OPT_FAILED = -1; - private final PackageManagerService mPackageManagerService; + private static final boolean DEBUG_DEXOPT = PackageManagerService.DEBUG_DEXOPT; + + private final Installer mInstaller; + private final Object mInstallLock; private final PowerManager.WakeLock mDexoptWakeLock; private volatile boolean mSystemReady; - PackageDexOptimizer(PackageManagerService packageManagerService) { - this.mPackageManagerService = packageManagerService; - PowerManager powerManager = (PowerManager)packageManagerService.mContext.getSystemService( - Context.POWER_SERVICE); - mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*dexopt*"); + PackageDexOptimizer(Installer installer, Object installLock, Context context, + String wakeLockTag) { + this.mInstaller = installer; + this.mInstallLock = installLock; + + PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag); + } + + protected PackageDexOptimizer(PackageDexOptimizer from) { + this.mInstaller = from.mInstaller; + this.mInstallLock = from.mInstallLock; + this.mDexoptWakeLock = from.mDexoptWakeLock; + this.mSystemReady = from.mSystemReady; } static boolean canOptimizePackage(PackageParser.Package pkg) { @@ -77,27 +90,19 @@ final class PackageDexOptimizer { * Performs dexopt on all code paths and libraries of the specified package for specified * instruction sets. * - * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on - * {@link PackageManagerService#mInstallLock}. + * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are + * synchronized on {@link #mInstallLock}. */ - int performDexOpt(PackageParser.Package pkg, String[] instructionSets, - boolean inclDependencies, boolean useProfiles, boolean extractOnly, boolean force) { - ArraySet<String> done; - if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { - done = new ArraySet<String>(); - done.add(pkg.packageName); - } else { - done = null; - } - synchronized (mPackageManagerService.mInstallLock) { + int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean useProfiles, + boolean extractOnly) { + synchronized (mInstallLock) { final boolean useLock = mSystemReady; if (useLock) { mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid)); mDexoptWakeLock.acquire(); } try { - return performDexOptLI(pkg, instructionSets, done, useProfiles, - extractOnly, force); + return performDexOptLI(pkg, instructionSets, useProfiles, extractOnly); } finally { if (useLock) { mDexoptWakeLock.release(); @@ -106,21 +111,42 @@ final class PackageDexOptimizer { } } + /** + * Determine whether the package should be skipped for the given instruction set. A return + * value of true means the package will be skipped. A return value of false means that the + * package will be further investigated, and potentially compiled. + */ + protected boolean shouldSkipBasedOnISA(PackageParser.Package pkg, String instructionSet) { + return pkg.mDexOptPerformed.contains(instructionSet); + } + + /** + * Adjust the given dexopt-needed value. Can be overridden to influence the decision to + * optimize or not (and in what way). + */ + protected int adjustDexoptNeeded(int dexoptNeeded) { + return dexoptNeeded; + } + + /** + * Adjust the given dexopt flags that will be passed to the installer. + */ + protected int adjustDexoptFlags(int dexoptFlags) { + return dexoptFlags; + } + + /** + * Update the package status after a successful compilation. + */ + protected void recordSuccessfulDexopt(PackageParser.Package pkg, String instructionSet) { + pkg.mDexOptPerformed.add(instructionSet); + } + private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, - ArraySet<String> done, boolean useProfiles, boolean extractOnly, boolean force) { + boolean useProfiles, boolean extractOnly) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); - if (done != null) { - done.add(pkg.packageName); - if (pkg.usesLibraries != null) { - performDexOptLibsLI(pkg.usesLibraries, instructionSets, done); - } - if (pkg.usesOptionalLibraries != null) { - performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, done); - } - } - if (!canOptimizePackage(pkg)) { return DEX_OPT_SKIPPED; } @@ -128,34 +154,26 @@ final class PackageDexOptimizer { final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - if (useProfiles) { - // If we do a profile guided compilation then we might recompile - // the same package if more profile information is available. - force = true; - } - final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); boolean performedDexOpt = false; final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); for (String dexCodeInstructionSet : dexCodeInstructionSets) { - if (!force && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) { + if (!useProfiles && shouldSkipBasedOnISA(pkg, dexCodeInstructionSet)) { + // Skip only if we do not use profiles since they might trigger a recompilation. continue; } for (String path : paths) { int dexoptNeeded; - if (force) { - dexoptNeeded = DexFile.DEX2OAT_NEEDED; - } else { - try { - dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName, - dexCodeInstructionSet, /* defer */false); - } catch (IOException ioe) { - Slog.w(TAG, "IOException reading apk: " + path, ioe); - return DEX_OPT_FAILED; - } + try { + dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName, + dexCodeInstructionSet, /* defer */false); + } catch (IOException ioe) { + Slog.w(TAG, "IOException reading apk: " + path, ioe); + return DEX_OPT_FAILED; } + dexoptNeeded = adjustDexoptNeeded(dexoptNeeded); if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) { // No dexopt needed and we don't use profiles. Nothing to do. @@ -174,21 +192,22 @@ final class PackageDexOptimizer { throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded); } + Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable + " extractOnly=" + extractOnly + " oatDir = " + oatDir); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final int dexFlags = + final int dexFlags = adjustDexoptFlags( (!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0) | (vmSafeMode ? DEXOPT_SAFEMODE : 0) | (debuggable ? DEXOPT_DEBUGGABLE : 0) | (extractOnly ? DEXOPT_EXTRACTONLY : 0) - | DEXOPT_BOOTCOMPLETE; + | DEXOPT_BOOTCOMPLETE); + try { - mPackageManagerService.mInstaller.dexopt(path, sharedGid, - pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir, - dexFlags, pkg.volumeUuid, useProfiles); + mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet, + dexoptNeeded, oatDir, dexFlags, pkg.volumeUuid, useProfiles); performedDexOpt = true; } catch (InstallerException e) { Slog.w(TAG, "Failed to dexopt", e); @@ -201,7 +220,7 @@ final class PackageDexOptimizer { // it isn't required. We therefore mark that this package doesn't need dexopt unless // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped // it. - pkg.mDexOptPerformed.add(dexCodeInstructionSet); + recordSuccessfulDexopt(pkg, dexCodeInstructionSet); } } @@ -232,8 +251,7 @@ final class PackageDexOptimizer { if (codePath.isDirectory()) { File oatDir = getOatDir(codePath); try { - mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(), - dexInstructionSet); + mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet); } catch (InstallerException e) { Slog.w(TAG, "Failed to create oat dir", e); return null; @@ -247,21 +265,36 @@ final class PackageDexOptimizer { return new File(codePath, OAT_DIR_NAME); } - private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, - ArraySet<String> done) { - for (String libName : libs) { - PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary( - libName); - if (libPkg != null && !done.contains(libName)) { - // TODO: Analyze and investigate if we (should) profile libraries. - // Currently this will do a full compilation of the library. - performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false, - /* extractOnly */ false, /* force */ false); - } - } - } - void systemReady() { mSystemReady = true; } + + /** + * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a + * dexopt path. + */ + public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer { + + public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock, + Context context, String wakeLockTag) { + super(installer, installLock, context, wakeLockTag); + } + + public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) { + super(from); + } + + @Override + protected boolean shouldSkipBasedOnISA(Package pkg, String instructionSet) { + // Forced compilation, never skip. + return false; + } + + @Override + protected int adjustDexoptNeeded(int dexoptNeeded) { + // Ensure compilation, no matter the current state. + // TODO: The return value is wrong when patchoat is needed. + return DexFile.DEX2OAT_NEEDED; + } + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b6905b4325bd..553a9a26de16 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -139,6 +139,7 @@ import android.content.pm.PackageManager.LegacyPackageDeleteObserver; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; +import android.content.pm.PackageParser.Package; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageStats; @@ -263,6 +264,7 @@ import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.io.PrintStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -277,6 +279,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -313,7 +316,12 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_INTENT_MATCHING = false; private static final boolean DEBUG_PACKAGE_SCANNING = false; private static final boolean DEBUG_VERIFY = false; - private static final boolean DEBUG_DEXOPT = false; + + // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService + // and PackageDexOptimizer. All these classes have their own flag to allow switching a single + // user, but by default initialize to this. + static final boolean DEBUG_DEXOPT = false; + private static final boolean DEBUG_ABI_SELECTION = false; private static final boolean DEBUG_EPHEMERAL = false; private static final boolean DEBUG_TRIAGED_MISSING = false; @@ -1990,7 +1998,8 @@ public class PackageManagerService extends IPackageManager.Stub { } mInstaller = installer; - mPackageDexOptimizer = new PackageDexOptimizer(this); + mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, + "*dexopt*"); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( @@ -3377,19 +3386,6 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - /** - * @hide - */ - PackageParser.Package findSharedNonSystemLibrary(String libName) { - synchronized (mPackages) { - PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName); - if (lib != null && lib.apk != null) { - return mPackages.get(lib.apk); - } - } - return null; - } - @Override public FeatureInfo[] getSystemAvailableFeatures() { Collection<FeatureInfo> featSet; @@ -6718,8 +6714,8 @@ public class PackageManagerService extends IPackageManager.Stub { try { synchronized (mInstallLock) { final String[] instructionSets = new String[] { targetInstructionSet }; - int result = mPackageDexOptimizer.performDexOpt(p, instructionSets, - true /* inclDependencies */, useProfiles, extractOnly, force); + int result = performDexOptInternalWithDependenciesLI(p, instructionSets, + useProfiles, extractOnly, force); return result == PackageDexOptimizer.DEX_OPT_PERFORMED; } } finally { @@ -6739,6 +6735,80 @@ public class PackageManagerService extends IPackageManager.Stub { return pkgs; } + private int performDexOptInternalWithDependenciesLI(PackageParser.Package p, + String instructionSets[], boolean useProfiles, boolean extractOnly, boolean force) { + // Select the dex optimizer based on the force parameter. + // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to + // allocate an object here. + PackageDexOptimizer pdo = force + ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) + : mPackageDexOptimizer; + + // Optimize all dependencies first. Note: we ignore the return value and march on + // on errors. + Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p); + if (!deps.isEmpty()) { + for (PackageParser.Package depPackage : deps) { + // TODO: Analyze and investigate if we (should) profile libraries. + // Currently this will do a full compilation of the library. + pdo.performDexOpt(depPackage, instructionSets, false /* useProfiles */, + false /* extractOnly */); + } + } + + return pdo.performDexOpt(p, instructionSets, useProfiles, extractOnly); + } + + Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) { + if (p.usesLibraries != null || p.usesOptionalLibraries != null) { + ArrayList<PackageParser.Package> retValue = new ArrayList<>(); + Set<String> collectedNames = new HashSet<>(); + findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames); + + retValue.remove(p); + + return retValue; + } else { + return Collections.emptyList(); + } + } + + private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p, + Collection<PackageParser.Package> collected, Set<String> collectedNames) { + if (!collectedNames.contains(p.packageName)) { + collectedNames.add(p.packageName); + collected.add(p); + + if (p.usesLibraries != null) { + findSharedNonSystemLibrariesRecursive(p.usesLibraries, collected, collectedNames); + } + if (p.usesOptionalLibraries != null) { + findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries, collected, + collectedNames); + } + } + } + + private void findSharedNonSystemLibrariesRecursive(Collection<String> libs, + Collection<PackageParser.Package> collected, Set<String> collectedNames) { + for (String libName : libs) { + PackageParser.Package libPkg = findSharedNonSystemLibrary(libName); + if (libPkg != null) { + findSharedNonSystemLibrariesRecursive(libPkg, collected, collectedNames); + } + } + } + + private PackageParser.Package findSharedNonSystemLibrary(String libName) { + synchronized (mPackages) { + PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName); + if (lib != null && lib.apk != null) { + return mPackages.get(lib.apk); + } + } + return null; + } + public void shutdown() { mPackageUsage.write(true); } @@ -6763,9 +6833,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Whoever is calling forceDexOpt wants a fully compiled package. // Don't use profiles since that may cause compilation to be skipped. - final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets, - true /* inclDependencies */, false /* useProfiles */, - false /* extractOnly */, true /* force */); + final int res = performDexOptInternalWithDependenciesLI(pkg, instructionSets, + false /* useProfiles */, false /* extractOnly */, true /* force */); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { @@ -7980,6 +8049,11 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.primaryCpuAbi = abi; } } + if (cpuAbiOverride != null && + cpuAbiOverride.equals(pkg.applicationInfo.secondaryCpuAbi)) { + pkg.applicationInfo.secondaryCpuAbi = pkg.applicationInfo.primaryCpuAbi; + pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride; + } } else { String[] abiList = (cpuAbiOverride != null) ? new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS; @@ -12842,8 +12916,11 @@ public class PackageManagerService extends IPackageManager.Stub { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - // Mark that we have an install time CPU ABI override. - pkg.cpuAbiOverride = args.abiOverride; + // If package doesn't declare API override, mark that we have an install + // time CPU ABI override. + if (TextUtils.isEmpty(pkg.cpuAbiOverride)) { + pkg.cpuAbiOverride = args.abiOverride; + } String pkgName = res.name = pkg.packageName; if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) { @@ -13016,7 +13093,9 @@ public class PackageManagerService extends IPackageManager.Stub { scanFlags |= SCAN_NO_DEX; try { - derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride, + String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ? + args.abiOverride : pkg.cpuAbiOverride); + derivePackageAbi(pkg, new File(pkg.codePath), abiOverride, true /* extract libs */); } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); @@ -13032,8 +13111,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Do not run PackageDexOptimizer through the local performDexOpt // method because `pkg` is not in `mPackages` yet. int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */, - false /* inclDependencies */, false /* useProfiles */, - true /* extractOnly */, false /* force */); + false /* useProfiles */, true /* extractOnly */); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (result == PackageDexOptimizer.DEX_OPT_FAILED) { String msg = "Extracking package failed for " + pkgName; @@ -18033,4 +18111,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); "Cannot call " + tag + " from UID " + callingUid); } } + + boolean isHistoricalPackageUsageAvailable() { + return mPackageUsage.isHistoricalPackageUsageAvailable(); + } } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 6452feeb6975..7ec945d74e52 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -302,7 +302,12 @@ class AppWindowToken extends WindowToken { void setWindowsExiting(boolean exiting) { for (int i = allAppWindows.size() - 1; i >= 0; i--) { WindowState win = allAppWindows.get(i); - win.mExiting = exiting; + // If the app already requested to remove its window, we don't modify + // its exiting state. Otherwise the stale window won't get removed on + // exit and could cause focus to be given to the wrong window. + if (!(win.mRemoveOnExit && win.mExiting)) { + win.mExiting = exiting; + } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 47bdb5cbad90..14e4fd59be29 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7260,8 +7260,18 @@ public class WindowManagerService extends IWindowManager.Stub mTaskPositioner = new TaskPositioner(this); mTaskPositioner.register(display); mInputMonitor.updateInputWindowsLw(true /*force*/); + + // We need to grab the touch focus so that the touch events during the + // resizing/scrolling are not sent to the app. 'win' is the main window + // of the app, it may not have focus since there might be other windows + // on top (eg. a dialog window). + WindowState transferFocusFromWin = win; + if (mCurrentFocus != null && mCurrentFocus != win + && mCurrentFocus.mAppToken == win.mAppToken) { + transferFocusFromWin = mCurrentFocus; + } if (!mInputManager.transferTouchFocus( - win.mInputChannel, mTaskPositioner.mServerChannel)) { + transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) { Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus"); mTaskPositioner.unregister(); mTaskPositioner = null; diff --git a/services/core/jni/com_android_server_AlarmManagerService.cpp b/services/core/jni/com_android_server_AlarmManagerService.cpp index 5cbb277a2f12..246ab0d259f4 100644 --- a/services/core/jni/com_android_server_AlarmManagerService.cpp +++ b/services/core/jni/com_android_server_AlarmManagerService.cpp @@ -40,6 +40,8 @@ #include <linux/android_alarm.h> #include <linux/rtc.h> +#include <memory> + namespace android { static const size_t N_ANDROID_TIMERFDS = ANDROID_ALARM_TYPE_COUNT + 1; @@ -323,14 +325,14 @@ static bool rtc_is_hctosys(unsigned int rtc_id) static int wall_clock_rtc() { - DIR *dir = opendir(rtc_sysfs); - if (!dir) { + std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(rtc_sysfs), closedir); + if (!dir.get()) { ALOGE("failed to open %s: %s", rtc_sysfs, strerror(errno)); return -1; } struct dirent *dirent; - while (errno = 0, dirent = readdir(dir)) { + while (errno = 0, dirent = readdir(dir.get())) { unsigned int rtc_id; int matched = sscanf(dirent->d_name, "rtc%u", &rtc_id); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 4022ac62bf96..cfe147e2cf13 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1463,6 +1463,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void securityLogSetLoggingEnabledProperty(boolean enabled) { SecurityLog.setLoggingEnabledProperty(enabled); } + + boolean securityLogGetLoggingEnabledProperty() { + return SecurityLog.getLoggingEnabledProperty(); + } + + boolean securityLogIsLoggingEnabled() { + return SecurityLog.isLoggingEnabled(); + } } /** @@ -1607,7 +1615,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mOwners.hasDeviceOwner()) { mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true"); disableDeviceLoggingIfNotCompliant(); - if (SecurityLog.getLoggingEnabledProperty()) { + if (mInjector.securityLogGetLoggingEnabledProperty()) { mSecurityLogMonitor.start(); } } else { @@ -2005,9 +2013,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void removeActiveAdminLocked(final ComponentName adminReceiver, final int userHandle) { final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); if (admin != null) { - synchronized (this) { - getUserData(userHandle).mRemovingAdmins.add(adminReceiver); - } + getUserData(userHandle).mRemovingAdmins.add(adminReceiver); + sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED, new BroadcastReceiver() { @@ -2813,14 +2820,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (admin == null) { return; } + // Active device/profile owners must remain active admins. + if (isDeviceOwner(adminReceiver, userHandle) + || isProfileOwner(adminReceiver, userHandle)) { + Slog.e(LOG_TAG, "Device/profile owner cannot be removed: component=" + + adminReceiver); + return; + } if (admin.getUid() != mInjector.binderGetCallingUid()) { - // Active device/profile owners must remain active admins. - if (isDeviceOwner(adminReceiver, userHandle) - || isProfileOwner(adminReceiver, userHandle)) { - Slog.e(LOG_TAG, "Device/profile owner cannot be removed: component=" + - adminReceiver); - return; - } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_DEVICE_ADMINS, null); } @@ -4472,7 +4479,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mInjector.binderRestoreCallingIdentity(ident); } - if (SecurityLog.isLoggingEnabled()) { + if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0); } } @@ -4502,7 +4509,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - if (SecurityLog.isLoggingEnabled()) { + if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1); } } @@ -4511,7 +4518,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void reportKeyguardDismissed() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - if (SecurityLog.isLoggingEnabled()) { + if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISSED); } } @@ -4520,7 +4527,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void reportKeyguardSecured() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - if (SecurityLog.isLoggingEnabled()) { + if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_SECURED); } } @@ -5473,9 +5480,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new SecurityException(e); } synchronized (this) { + final ComponentName deviceOwnerComponent = mOwners.getDeviceOwnerComponent(); + final int deviceOwnerUserId = mOwners.getDeviceOwnerUserId(); if (!mOwners.hasDeviceOwner() - || !mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName) - || (mOwners.getDeviceOwnerUserId() != UserHandle.getUserId(callingUid))) { + || !deviceOwnerComponent.getPackageName().equals(packageName) + || (deviceOwnerUserId != UserHandle.getUserId(callingUid))) { throw new SecurityException( "clearDeviceOwner can only be called by the device owner"); } @@ -5487,8 +5496,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { admin.forceEphemeralUsers = false; mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers); } - - clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM)); + clearUserPoliciesLocked(deviceOwnerUserId); mOwners.clearDeviceOwner(); mOwners.writeDeviceOwner(); @@ -5498,6 +5506,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = mInjector.binderClearCallingIdentity(); try { mInjector.getIBackupManager().setBackupServiceActive(UserHandle.USER_SYSTEM, true); + + removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId); } catch (RemoteException e) { throw new IllegalStateException("Failed reactivating backup service.", e); } finally { @@ -5538,9 +5548,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { admin.disableCamera = false; admin.userRestrictions = null; - clearUserPoliciesLocked(callingUser); + clearUserPoliciesLocked(userId); mOwners.removeProfileOwner(userId); mOwners.writeProfileOwner(userId); + + final long ident = mInjector.binderClearCallingIdentity(); + try { + removeActiveAdminLocked(who, userId); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } } } @@ -5568,8 +5585,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mLockPatternUtils.getDeviceOwnerInfo(); } - private void clearUserPoliciesLocked(UserHandle userHandle) { - int userId = userHandle.getIdentifier(); + private void clearUserPoliciesLocked(int userId) { // Reset some of the user-specific policies DevicePolicyData policy = getUserData(userId); policy.mPermissionPolicy = DevicePolicyManager.PERMISSION_POLICY_PROMPT; @@ -5583,8 +5599,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { mIPackageManager.updatePermissionFlagsForAllApps( PackageManager.FLAG_PERMISSION_POLICY_FIXED, - 0 /* flagValues */, userHandle.getIdentifier()); - pushUserRestrictions(userHandle.getIdentifier()); + 0 /* flagValues */, userId); + pushUserRestrictions(userId); } catch (RemoteException re) { } finally { mInjector.binderRestoreCallingIdentity(ident); @@ -7244,7 +7260,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (managedUserId < 0) { return; } - if (getCrossProfileCallerIdDisabledForUser(managedUserId)) { + if (isCrossProfileQuickContactDisabled(managedUserId)) { if (VERBOSE_LOG) { Log.v(LOG_TAG, "Cross-profile contacts access disabled for user " + managedUserId); @@ -7260,6 +7276,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** + * @return true if cross-profile QuickContact is disabled + */ + private boolean isCrossProfileQuickContactDisabled(int userId) { + return getCrossProfileCallerIdDisabledForUser(userId) + && getCrossProfileContactsSearchDisabledForUser(userId); + } + + /** * @return the user ID of the managed user that is linked to the current user, if any. * Otherwise -1. */ @@ -8344,7 +8368,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkNotNull(admin); synchronized (this) { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - return SecurityLog.getLoggingEnabledProperty(); + return mInjector.securityLogGetLoggingEnabledProperty(); } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 254a37a7adfa..b64db578decb 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -80,6 +80,7 @@ import com.android.server.os.SchedulingPolicyService; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; +import com.android.server.pm.OtaDexoptService; import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; import com.android.server.power.PowerManagerService; @@ -1097,6 +1098,18 @@ public final class SystemServer { } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + // Manages A/B OTA dexopting. + boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt", + false); + if (!disableOtaDexopt) { + traceBeginAndSlog("StartOtaDexOptService"); + try { + OtaDexoptService.main(mSystemContext, mPackageManagerService); + } catch (Throwable e) { + reportWtf("starting BackgroundDexOptService", e); + } + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } } mSystemServiceManager.startService(LauncherAppsService.class); diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 87ac846a2a43..62c635db8578 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -18,6 +18,7 @@ package android.net.ip; import android.content.Context; import android.net.DhcpResults; +import android.net.InterfaceConfiguration; import android.net.LinkProperties; import android.net.LinkProperties.ProvisioningChange; import android.net.RouteInfo; @@ -86,8 +87,8 @@ public class IpManager extends StateMachine { // TODO: Kill with fire once DHCP and static configuration are moved // out of WifiStateMachine. - public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults, int reason) {} - public void onIPv4ProvisioningFailure(int reason) {} + public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults) {} + public void onIPv4ProvisioningFailure() {} public void onProvisioningSuccess(LinkProperties newLp) {} public void onProvisioningFailure(LinkProperties newLp) {} @@ -216,8 +217,8 @@ public class IpManager extends StateMachine { } // TODO: Kill with fire once DHCPv4/static config is moved into IpManager. - public void updateWithDhcpResults(DhcpResults dhcpResults, int reason) { - sendMessage(CMD_UPDATE_DHCPV4_RESULTS, reason, 0, dhcpResults); + public void updateWithDhcpResults(DhcpResults dhcpResults) { + sendMessage(CMD_UPDATE_DHCPV4_RESULTS, dhcpResults); } @@ -382,7 +383,15 @@ public class IpManager extends StateMachine { } }); - // TODO: Check mStaticIpConfig and handle accordingly. + // If we have a StaticIpConfiguration attempt to apply it and + // handle the result accordingly. + if (mStaticIpConfig != null) { + if (applyStaticIpConfig()) { + sendMessage(CMD_UPDATE_DHCPV4_RESULTS, new DhcpResults(mStaticIpConfig)); + } else { + sendMessage(CMD_UPDATE_DHCPV4_RESULTS); + } + } } @Override @@ -415,15 +424,14 @@ public class IpManager extends StateMachine { case CMD_UPDATE_DHCPV4_RESULTS: final DhcpResults dhcpResults = (DhcpResults) msg.obj; - final int reason = msg.arg1; if (dhcpResults != null) { mDhcpResults = new DhcpResults(dhcpResults); setLinkProperties(assembleLinkProperties()); - mCallback.onIPv4ProvisioningSuccess(dhcpResults, reason); + mCallback.onIPv4ProvisioningSuccess(dhcpResults); } else { mDhcpResults = null; setLinkProperties(assembleLinkProperties()); - mCallback.onIPv4ProvisioningFailure(reason); + mCallback.onIPv4ProvisioningFailure(); } break; @@ -457,5 +465,19 @@ public class IpManager extends StateMachine { return HANDLED; } + private boolean applyStaticIpConfig() { + final InterfaceConfiguration ifcg = new InterfaceConfiguration(); + ifcg.setLinkAddress(mStaticIpConfig.ipAddress); + ifcg.setInterfaceUp(); + try { + mNwService.setInterfaceConfig(mInterfaceName, ifcg); + if (DBG) Log.d(TAG, "Static IP configuration succeeded"); + } catch (IllegalStateException | RemoteException e) { + Log.e(TAG, "Static IP configuration failed: ", e); + return false; + } + + return true; + } } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index bd37f4a75e6f..db2a9ad3223c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -291,5 +291,15 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi void securityLogSetLoggingEnabledProperty(boolean enabled) { context.settings.securityLogSetLoggingEnabledProperty(enabled); } + + @Override + boolean securityLogGetLoggingEnabledProperty() { + return context.settings.securityLogGetLoggingEnabledProperty(); + } + + @Override + boolean securityLogIsLoggingEnabled() { + return context.settings.securityLogIsLoggingEnabled(); + } } } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 87569b7a6caa..64f60d93628b 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -559,6 +559,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { expected.getMessage().contains("already has a device owner")); } + // DO admin can't be deactivated. + dpm.removeActiveAdmin(admin1); + assertTrue(dpm.isAdminActive(admin1)); + // TODO Test getDeviceOwnerName() too. To do so, we need to change // DPMS.getApplicationLabel() because Context.createPackageContextAsUser() is not mockable. } @@ -763,6 +767,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser()); + dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER); + + assertTrue(dpm.isAdminActive(admin1)); + assertFalse(dpm.isRemovingAdmin(admin1, UserHandle.USER_SYSTEM)); + // Set up other mocks. when(mContext.userManager.getUserRestrictions()).thenReturn(new Bundle()); @@ -770,11 +779,21 @@ public class DevicePolicyManagerTest extends DpmTestBase { doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(mContext.packageManager).getPackageUidAsUser( eq(admin1.getPackageName()), anyInt()); + reset(mContext.userManagerInternal); dpm.clearDeviceOwnerApp(admin1.getPackageName()); // Now DO shouldn't be set. assertNull(dpm.getDeviceOwnerComponentOnAnyUser()); + verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions( + eq(UserHandle.USER_SYSTEM), + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions() + ); + + assertTrue(dpm.isAdminActive(admin1)); + assertTrue(dpm.isRemovingAdmin(admin1, UserHandle.USER_SYSTEM)); + // TODO Check other calls. } @@ -823,6 +842,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { public void testSetProfileOwner() throws Exception { setAsProfileOwner(admin1); + // PO admin can't be deactivated. + dpm.removeActiveAdmin(admin1); + assertTrue(dpm.isAdminActive(admin1)); + // Try setting DO on the same user, which should fail. setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID); dpm.setActiveAdmin(admin2, /* refreshing= */ true, DpmMockContext.CALLER_USER_HANDLE); @@ -835,6 +858,22 @@ public class DevicePolicyManagerTest extends DpmTestBase { } } + public void testClearProfileOwner() throws Exception { + setAsProfileOwner(admin1); + + mContext.binder.callingUid = DpmMockContext.CALLER_UID; + + assertTrue(dpm.isProfileOwnerApp(admin1.getPackageName())); + assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE)); + + // Clear + dpm.clearProfileOwner(admin1); + + // Check + assertFalse(dpm.isProfileOwnerApp(admin1.getPackageName())); + assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE)); + } + public void testSetProfileOwner_failures() throws Exception { // TODO Test more failure cases. Basically test all chacks in enforceCanSetProfileOwner(). } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 651873230082..7a000985a5ca 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -198,6 +198,14 @@ public class DpmMockContext extends MockContext { void securityLogSetLoggingEnabledProperty(boolean enabled) { } + + public boolean securityLogGetLoggingEnabledProperty() { + return false; + } + + public boolean securityLogIsLoggingEnabled() { + return false; + } } public final Context realTestContext; diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index 56eb7ec5fb78..d45938cb9b85 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -348,11 +348,6 @@ public final class Phone { } private void checkCallTree(ParcelableCall parcelableCall) { - if (parcelableCall.getParentCallId() != null && - !mCallByTelecomCallId.containsKey(parcelableCall.getParentCallId())) { - Log.wtf(this, "ParcelableCall %s has nonexistent parent %s", - parcelableCall.getId(), parcelableCall.getParentCallId()); - } if (parcelableCall.getChildCallIds() != null) { for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) { if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) { diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index af36b3e41eb2..95c8db54498c 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -243,4 +243,9 @@ interface ITelecomService { * @see TelecomServiceImpl#setDefaultDialer */ boolean setDefaultDialer(in String packageName); + + /** + * @see TelecomServiceImpl#launchManageBlockedNumbersActivity + **/ + void launchManageBlockedNumbersActivity(in String callingPackageName); } diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index 8443490feb00..9eb1304c6ab6 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -187,6 +187,13 @@ public class DisconnectCause { */ public static final int CDMA_ALREADY_ACTIVATED = 49; + /** + * The call was terminated because it is not possible to place a video call while TTY is + * enabled. + * {@hide} + */ + public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50; + //********************************************************************************************* // When adding a disconnect type: // 1) Please assign the new type the next id value below. @@ -202,7 +209,7 @@ public class DisconnectCause { public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED; /** Largest valid value for call disconnect codes. */ - public static final int MAXIMUM_VALID_VALUE = CDMA_ALREADY_ACTIVATED; + public static final int MAXIMUM_VALID_VALUE = VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED; /** Private constructor to avoid class instantiation. */ private DisconnectCause() { @@ -310,6 +317,8 @@ public class DisconnectCause { return "IMS_MERGED_SUCCESSFULLY"; case CDMA_ALREADY_ACTIVATED: return "CDMA_ALREADY_ACTIVATED"; + case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED: + return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED"; default: return "INVALID: " + cause; } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 7d5645ed7337..429839f6f6f9 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -77,6 +77,21 @@ public interface RILConstants { int SIM_SAP_MSG_SIZE_TOO_SMALL = 34; int SIM_SAP_CONNECT_OK_CALL_ONGOING = 35; int LCE_NOT_SUPPORTED = 36; /* Link Capacity Estimation (LCE) not supported */ + int NO_MEMORY = 37; /* Not sufficient memory to process the request */ + int INTERNAL_ERR = 38; /* Hit unexpected vendor internal error scenario */ + int SYSTEM_ERR = 39; /* Hit platform or system error */ + int MODEM_ERR = 40; /* Hit unexpected modem error */ + int INVALID_STATE = 41; /* Can not process the request as vendor RIL is in + invalid state. */ + int NO_RESOURCES = 42; /* Not sufficient resource to process the request */ + int SIM_ERR = 43; /* Received error from SIM card */ + int INVALID_ARGUMENTS = 44; /* Received invalid arguments in request */ + int INVALID_SIM_STATE = 45; /* Can not process the request in current SIM state */ + int INVALID_MODEM_STATE = 46; /* Can not process the request in current Modem state */ + int INVALID_CALL_ID = 47; /* Received invalid call id in request */ + int NO_SMS_TO_ACK = 48; /* ACK received when there is no SMS to ack */ + int NETWORK_ERR = 49; /* Received error from network */ + int REQUEST_RATE_LIMITED = 50; /* Operation denied due to overly-frequent requests */ /* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */ diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index a4f4ba928855..f74b93abd796 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -30,6 +30,7 @@ sources := \ compile/PseudolocaleGenerator.cpp \ compile/Pseudolocalizer.cpp \ compile/XmlIdCollector.cpp \ + filter/ConfigFilter.cpp \ flatten/Archive.cpp \ flatten/TableFlattener.cpp \ flatten/XmlFlattener.cpp \ @@ -71,6 +72,7 @@ testSources := \ compile/PseudolocaleGenerator_test.cpp \ compile/Pseudolocalizer_test.cpp \ compile/XmlIdCollector_test.cpp \ + filter/ConfigFilter_test.cpp \ flatten/FileExportWriter_test.cpp \ flatten/TableFlattener_test.cpp \ flatten/XmlFlattener_test.cpp \ diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index 03691568ed47..6acf3b09d301 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -70,7 +70,7 @@ static inline bool isNumber(const std::string& str) { return std::all_of(std::begin(str), std::end(str), ::isdigit); } -bool LocaleValue::initFromFilterString(const std::string& str) { +bool LocaleValue::initFromFilterString(const StringPiece& str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". std::vector<std::string> parts = util::splitAndLowercase(str, '_'); diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h index ceec764ba4fd..b1c80ab27641 100644 --- a/tools/aapt2/Locale.h +++ b/tools/aapt2/Locale.h @@ -17,6 +17,8 @@ #ifndef AAPT_LOCALE_VALUE_H #define AAPT_LOCALE_VALUE_H +#include "util/StringPiece.h" + #include <androidfw/ResourceTypes.h> #include <string> #include <vector> @@ -37,7 +39,7 @@ struct LocaleValue { /** * Initialize this LocaleValue from a config string. */ - bool initFromFilterString(const std::string& config); + bool initFromFilterString(const StringPiece& config); /** * Initialize this LocaleValue from parts of a vector. diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp new file mode 100644 index 000000000000..68a017d247f6 --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016 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 "ConfigDescription.h" +#include "filter/ConfigFilter.h" + +#include <androidfw/ResourceTypes.h> + +namespace aapt { + +void AxisConfigFilter::addConfig(ConfigDescription config) { + uint32_t diffMask = ConfigDescription::defaultConfig().diff(config); + + // Ignore the version + diffMask &= ~android::ResTable_config::CONFIG_VERSION; + + // Ignore any densities. Those are best handled in --preferred-density + if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) { + config.density = 0; + diffMask &= ~android::ResTable_config::CONFIG_DENSITY; + } + + mConfigs.insert(std::make_pair(config, diffMask)); + mConfigMask |= diffMask; +} + +bool AxisConfigFilter::match(const ConfigDescription& config) const { + const uint32_t mask = ConfigDescription::defaultConfig().diff(config); + if ((mConfigMask & mask) == 0) { + // The two configurations don't have any common axis. + return true; + } + + uint32_t matchedAxis = 0; + for (const auto& entry : mConfigs) { + const ConfigDescription& target = entry.first; + const uint32_t diffMask = entry.second; + uint32_t diff = target.diff(config); + if ((diff & diffMask) == 0) { + // Mark the axis that was matched. + matchedAxis |= diffMask; + } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) { + // If the locales differ, but the languages are the same and + // the locale we are matching only has a language specified, + // we match. + if (config.language[0] && + memcmp(config.language, target.language, sizeof(config.language)) == 0) { + if (config.country[0] == 0) { + matchedAxis |= android::ResTable_config::CONFIG_LOCALE; + } + } + } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) { + // Special case if the smallest screen width doesn't match. We check that the + // config being matched has a smaller screen width than the filter specified. + if (config.smallestScreenWidthDp != 0 && + config.smallestScreenWidthDp < target.smallestScreenWidthDp) { + matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; + } + } + } + return matchedAxis == (mConfigMask & mask); +} + +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h new file mode 100644 index 000000000000..36e9c44255e4 --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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_FILTER_CONFIGFILTER_H +#define AAPT_FILTER_CONFIGFILTER_H + +#include "ConfigDescription.h" + +#include <set> +#include <utility> + +namespace aapt { + +/** + * Matches ConfigDescriptions based on some pattern. + */ +class IConfigFilter { +public: + virtual ~IConfigFilter() = default; + + /** + * Returns true if the filter matches the configuration, false otherwise. + */ + virtual bool match(const ConfigDescription& config) const = 0; +}; + +/** + * Implements config axis matching. An axis is one component of a configuration, like screen + * density or locale. If an axis is specified in the filter, and the axis is specified in + * the configuration to match, they must be compatible. Otherwise the configuration to match is + * accepted. + * + * Used when handling "-c" options. + */ +class AxisConfigFilter : public IConfigFilter { +public: + void addConfig(ConfigDescription config); + + bool match(const ConfigDescription& config) const override; + +private: + std::set<std::pair<ConfigDescription, uint32_t>> mConfigs; + uint32_t mConfigMask = 0; +}; + +} // namespace aapt + +#endif /* AAPT_FILTER_CONFIGFILTER_H */ diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp new file mode 100644 index 000000000000..f6b49557306d --- /dev/null +++ b/tools/aapt2/filter/ConfigFilter_test.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 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 "filter/ConfigFilter.h" +#include "test/Common.h" + +#include <gtest/gtest.h> + +namespace aapt { + +TEST(ConfigFilterTest, EmptyFilterMatchesAnything) { + AxisConfigFilter filter; + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr-rFR")); + filter.addConfig(test::parseConfigOrDie("sw360dp")); + filter.addConfig(test::parseConfigOrDie("normal")); + filter.addConfig(test::parseConfigOrDie("en-rUS")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("en"))); +} + +TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); + + EXPECT_FALSE(filter.match(test::parseConfigOrDie("de"))); +} + +TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr-rFR")); + filter.addConfig(test::parseConfigOrDie("en-rUS")); + filter.addConfig(test::parseConfigOrDie("normal")); + filter.addConfig(test::parseConfigOrDie("large")); + filter.addConfig(test::parseConfigOrDie("xxhdpi")); + filter.addConfig(test::parseConfigOrDie("sw320dp")); + + EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("sw600dp")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("de-rDE")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("de"))); +} + +TEST(ConfigFilterTest, IgnoresVersion) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("normal-v4")); + + // The configs don't match on any axis besides version, which should be ignored. + EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13"))); +} + +TEST(ConfigFilterTest, MatchesConfigWithRegion) { + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("kok")); + filter.addConfig(test::parseConfigOrDie("kok-rIN")); + filter.addConfig(test::parseConfigOrDie("kok-v419")); + + EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN"))); +} + +} // namespace aapt diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 3ecb2c4113d5..fd76e887ab2a 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -17,8 +17,10 @@ #include "AppInfo.h" #include "Debug.h" #include "Flags.h" +#include "Locale.h" #include "NameMangler.h" #include "compile/IdAssigner.h" +#include "filter/ConfigFilter.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" @@ -64,7 +66,7 @@ struct LinkOptions { std::vector<std::string> extensionsToNotCompress; Maybe<std::u16string> privateSymbols; ManifestFixerOptions manifestFixerOptions; - + IConfigFilter* configFilter = nullptr; }; struct LinkContext : public IAaptContext { @@ -97,8 +99,8 @@ struct LinkContext : public IAaptContext { class LinkCommand { public: - LinkCommand(const LinkOptions& options) : - mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) { + LinkCommand(LinkContext* context, const LinkOptions& options) : + mOptions(options), mContext(context), mFinalTable(), mFileCollection(nullptr) { std::unique_ptr<io::FileCollection> fileCollection = util::make_unique<io::FileCollection>(); @@ -117,14 +119,14 @@ public: AssetManagerSymbolTableBuilder builder; for (const std::string& path : mOptions.includePaths) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path"); + mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); } std::unique_ptr<android::AssetManager> assetManager = util::make_unique<android::AssetManager>(); int32_t cookie = 0; if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) { - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage(path) << "failed to load include path"); return {}; } @@ -135,7 +137,7 @@ public: std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) { std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(&mContext, table.get(), source, data, len); + BinaryResourceParser parser(mContext, table.get(), source, data, len); if (!parser.parse()) { return {}; } @@ -207,7 +209,7 @@ public: IArchiveWriter* writer) { std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } @@ -215,7 +217,7 @@ public: std::string errorStr; ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr); if (offset < 0) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr); + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr); return false; } @@ -228,7 +230,7 @@ public: } } - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); return false; } @@ -252,9 +254,9 @@ public: */ bool verifyNoExternalPackages() { auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { - return mContext.getCompilationPackage() != pkg->name || + return mContext->getCompilationPackage() != pkg->name || !pkg->id || - pkg->id.value() != mContext.getPackageId(); + pkg->id.value() != mContext->getPackageId(); }; bool error = false; @@ -270,13 +272,13 @@ public: // 'android' package. This is due to legacy reasons. if (valueCast<Id>(configValue.value.get()) && package->name == u"android") { - mContext.getDiagnostics()->warn( + mContext->getDiagnostics()->warn( DiagMessage(configValue.value->getSource()) << "generated id '" << resName << "' for external package '" << package->name << "'"); } else { - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage(configValue.value->getSource()) << "defined resource '" << resName << "' for external package '" << package->name @@ -297,9 +299,9 @@ public: std::unique_ptr<IArchiveWriter> makeArchiveWriter() { if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); + return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); } else { - return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); + return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); } } @@ -308,7 +310,7 @@ public: TableFlattenerOptions options = {}; options.useExtendedChunks = mOptions.staticLib; TableFlattener flattener(&buffer, options); - if (!flattener.consume(&mContext, table)) { + if (!flattener.consume(mContext, table)) { return false; } @@ -320,7 +322,7 @@ public: } } - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage() << "failed to write resources.arsc to archive"); return false; } @@ -332,7 +334,7 @@ public: options.keepRawValues = mOptions.staticLib; options.maxSdkLevel = maxSdkLevel; XmlFlattener flattener(&buffer, options); - if (!flattener.consume(&mContext, xmlRes)) { + if (!flattener.consume(mContext, xmlRes)) { return false; } @@ -343,7 +345,7 @@ public: } } } - mContext.getDiagnostics()->error( + mContext->getDiagnostics()->error( DiagMessage() << "failed to write " << path << " to archive"); return false; } @@ -361,13 +363,13 @@ public: std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } JavaClassGenerator generator(table, javaOptions); if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { - mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); + mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); return false; } return true; @@ -380,24 +382,24 @@ public: std::string outPath = mOptions.generateJavaClassPath.value(); file::appendPath(&outPath, - file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage()))); + file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage()))); file::mkdirs(outPath); file::appendPath(&outPath, "Manifest.java"); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } ManifestClassGenerator generator; - if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(), + if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(), manifestXml, &fout)) { return false; } if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } return true; @@ -410,13 +412,13 @@ public: std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } proguard::writeKeepSet(&fout, keepSet); if (!fout) { - mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); + mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } return true; @@ -425,7 +427,7 @@ public: bool mergeStaticLibrary(const std::string& input) { // TODO(adamlesinski): Load resources from a static library APK and merge the table into // TableMerger. - mContext.getDiagnostics()->warn(DiagMessage() + mContext->getDiagnostics()->warn(DiagMessage() << "linking static libraries not supported yet: " << input); return true; @@ -433,12 +435,12 @@ public: bool mergeResourceTable(io::IFile* file, bool override) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); + mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); } std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } @@ -460,7 +462,7 @@ public: bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); + mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); } bool result = false; @@ -477,12 +479,12 @@ public: // Add the exports of this file to the table. for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { if (exportedSymbol.name.package.empty()) { - exportedSymbol.name.package = mContext.getCompilationPackage().toString(); + exportedSymbol.name.package = mContext->getCompilationPackage().toString(); } ResourceNameRef resName = exportedSymbol.name; - Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName( + Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( exportedSymbol.name); if (mangledName) { resName = mangledName.value(); @@ -491,7 +493,7 @@ public: std::unique_ptr<Id> id = util::make_unique<Id>(); id->setSource(fileDesc->source.withLine(exportedSymbol.line)); bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id), - mContext.getDiagnostics()); + mContext->getDiagnostics()); if (!result) { return false; } @@ -507,7 +509,7 @@ public: std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( input, &errorStr); if (!collection) { - mContext.getDiagnostics()->error(DiagMessage(input) << errorStr); + mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); return false; } @@ -543,12 +545,12 @@ public: // Try opening the file and looking for an Export header. std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open"); + mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); return false; } std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( - src, data->data(), data->size(), mContext.getDiagnostics()); + src, data->data(), data->size(), mContext->getDiagnostics()); if (resourceFile) { return mergeCompiledFile(file, std::move(resourceFile), override); } @@ -564,62 +566,63 @@ public: int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, - mContext.getDiagnostics()); + mContext->getDiagnostics()); if (!manifestXml) { return 1; } if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { - mContext.mCompilationPackage = maybeAppInfo.value().package; + mContext->mCompilationPackage = maybeAppInfo.value().package; } else { - mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "no package specified in <manifest> tag"); return 1; } - if (!util::isJavaPackageName(mContext.mCompilationPackage)) { - mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) + if (!util::isJavaPackageName(mContext->mCompilationPackage)) { + mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "invalid package name '" - << mContext.mCompilationPackage + << mContext->mCompilationPackage << "'"); return 1; } - mContext.mNameMangler = util::make_unique<NameMangler>( - NameManglerPolicy{ mContext.mCompilationPackage }); + mContext->mNameMangler = util::make_unique<NameMangler>( + NameManglerPolicy{ mContext->mCompilationPackage }); - if (mContext.mCompilationPackage == u"android") { - mContext.mPackageId = 0x01; + if (mContext->mCompilationPackage == u"android") { + mContext->mPackageId = 0x01; } else { - mContext.mPackageId = 0x7f; + mContext->mPackageId = 0x7f; } - mContext.mSymbols = createSymbolTableFromIncludePaths(); - if (!mContext.mSymbols) { + mContext->mSymbols = createSymbolTableFromIncludePaths(); + if (!mContext->mSymbols) { return 1; } TableMergerOptions tableMergerOptions; tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; - mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions); + tableMergerOptions.filter = mOptions.configFilter; + mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); if (mOptions.verbose) { - mContext.getDiagnostics()->note( - DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' " - << "with package ID " << std::hex << (int) mContext.mPackageId); + mContext->getDiagnostics()->note( + DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' " + << "with package ID " << std::hex << (int) mContext->mPackageId); } for (const std::string& input : inputFiles) { if (!processFile(input, false)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); + mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); return 1; } } for (const std::string& input : mOptions.overlayFiles) { if (!processFile(input, true)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); + mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); return 1; } } @@ -630,8 +633,8 @@ public: if (!mOptions.staticLib) { PrivateAttributeMover mover; - if (!mover.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error( + if (!mover.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error( DiagMessage() << "failed moving private attributes"); return 1; } @@ -639,23 +642,23 @@ public: { IdAssigner idAssigner; - if (!idAssigner.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); + if (!idAssigner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); return 1; } } - mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{ - mContext.mCompilationPackage, mTableMerger->getMergedPackages() }); - mContext.mSymbols = JoinedSymbolTableBuilder() + mContext->mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{ + mContext->mCompilationPackage, mTableMerger->getMergedPackages() }); + mContext->mSymbols = JoinedSymbolTableBuilder() .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable)) - .addSymbolTable(std::move(mContext.mSymbols)) + .addSymbolTable(std::move(mContext->mSymbols)) .build(); { ReferenceLinker linker; - if (!linker.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed linking references"); + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); return 1; } } @@ -664,24 +667,24 @@ public: std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); if (!archiveWriter) { - mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive"); + mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); return 1; } bool error = false; { ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(&mContext, manifestXml.get())) { + if (!manifestFixer.consume(mContext, manifestXml.get())) { error = true; } // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. - manifestXml->file.name.package = mContext.getCompilationPackage().toString(); + manifestXml->file.name.package = mContext->getCompilationPackage().toString(); XmlReferenceLinker manifestLinker; - if (manifestLinker.consume(&mContext, manifestXml.get())) { + if (manifestLinker.consume(mContext, manifestXml.get())) { if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), manifestXml.get(), &proguardKeepSet)) { @@ -704,7 +707,7 @@ public: } if (error) { - mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest"); + mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); return 1; } @@ -718,13 +721,13 @@ public: (util::stringEndsWith<char>(path, ".xml.flat") || util::stringEndsWith<char>(path, ".xml"))) { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "linking " << path); + mContext->getDiagnostics()->note(DiagMessage() << "linking " << path); } io::IFile* file = fileToMerge.file; std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file->getSource()) + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return 1; } @@ -733,9 +736,9 @@ public: if (util::stringEndsWith<char>(path, ".flat")) { xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(), - mContext.getDiagnostics()); + mContext->getDiagnostics()); } else { - xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(), + xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(), file->getSource()); } @@ -751,7 +754,7 @@ public: }; XmlReferenceLinker xmlLinker; - if (xmlLinker.consume(&mContext, xmlRes.get())) { + if (xmlLinker.consume(mContext, xmlRes.get())) { if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), &proguardKeepSet)) { error = true; @@ -778,14 +781,14 @@ public: xmlRes->file.config.sdkVersion = sdkLevel; std::string genResourcePath = ResourceUtils::buildResourceFileName( - xmlRes->file, mContext.getNameMangler()); + xmlRes->file, mContext->getNameMangler()); bool added = mFinalTable.addFileReference( xmlRes->file.name, xmlRes->file.config, xmlRes->file.source, util::utf8ToUtf16(genResourcePath), - mContext.getDiagnostics()); + mContext->getDiagnostics()); if (!added) { error = true; continue; @@ -804,7 +807,7 @@ public: } } else { if (mOptions.verbose) { - mContext.getDiagnostics()->note(DiagMessage() << "copying " << path); + mContext->getDiagnostics()->note(DiagMessage() << "copying " << path); } if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath, @@ -815,20 +818,20 @@ public: } if (error) { - mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); return 1; } if (!mOptions.noAutoVersion) { AutoVersioner versioner; - if (!versioner.consume(&mContext, &mFinalTable)) { - mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles"); + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); return 1; } } if (!flattenTable(&mFinalTable, archiveWriter.get())) { - mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); + mContext->getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); return 1; } @@ -840,8 +843,8 @@ public: options.useFinal = false; } - const StringPiece16 actualPackage = mContext.getCompilationPackage(); - StringPiece16 outputPackage = mContext.getCompilationPackage(); + const StringPiece16 actualPackage = mContext->getCompilationPackage(); + StringPiece16 outputPackage = mContext->getCompilationPackage(); if (mOptions.customJavaPackage) { // Override the output java package to the custom one. outputPackage = mOptions.customJavaPackage.value(); @@ -852,7 +855,7 @@ public: // to the original package, and private and public symbols to the private package. options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(), + if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), outputPackage, options)) { return 1; } @@ -886,7 +889,7 @@ public: private: LinkOptions mOptions; - LinkContext mContext; + LinkContext* mContext; ResourceTable mFinalTable; ResourceTable mLocalFileTable; @@ -907,6 +910,7 @@ int link(const std::vector<StringPiece>& args) { Maybe<std::string> versionCode, versionName; Maybe<std::string> customJavaPackage; std::vector<std::string> extraJavaPackages; + Maybe<std::string> configs; bool legacyXFlag = false; bool requireLocalization = false; Flags flags = Flags() @@ -928,6 +932,8 @@ int link(const std::vector<StringPiece>& args) { &legacyXFlag) .optionalSwitch("-z", "Require localization of strings marked 'suggested'", &requireLocalization) + .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" + "is all configurations", &configs) .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " "by -o", &options.outputToDirectory) @@ -967,6 +973,8 @@ int link(const std::vector<StringPiece>& args) { return 1; } + LinkContext context; + if (privateSymbolsPackage) { options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); } @@ -1011,7 +1019,31 @@ int link(const std::vector<StringPiece>& args) { } } - LinkCommand cmd(options); + AxisConfigFilter filter; + if (configs) { + for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { + ConfigDescription config; + LocaleValue lv; + if (lv.initFromFilterString(configStr)) { + lv.writeTo(&config); + } else if (!ConfigDescription::parse(configStr, &config)) { + context.getDiagnostics()->error( + DiagMessage() << "invalid config '" << configStr << "' for -c option"); + return 1; + } + + if (config.density != 0) { + context.getDiagnostics()->warn( + DiagMessage() << "ignoring density '" << config << "' for -c option"); + } else { + filter.addConfig(config); + } + } + + options.configFilter = &filter; + } + + LinkCommand cmd(&context, options); return cmd.run(flags.getArgs()); } diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 27a23bd65103..e01a00401133 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -100,7 +100,7 @@ bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& package return false; } - mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{ + mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{ f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) }; return true; }; @@ -201,6 +201,9 @@ bool TableMerger::doMerge(const Source& src, auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(), srcValue.config, cmp::lessThanConfig); + const bool stripConfig = mOptions.filter ? + !mOptions.filter->match(srcValue.config) : false; + if (iter != dstEntry->values.end() && iter->config == srcValue.config) { const int collisionResult = ResourceTable::resolveValueCollision( iter->value.get(), srcValue.value.get()); @@ -224,11 +227,15 @@ bool TableMerger::doMerge(const Source& src, continue; } - } else { + } else if (!stripConfig){ // Insert a place holder value. We will fill it in below. iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config }); } + if (stripConfig) { + continue; + } + if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) { std::unique_ptr<FileReference> newFileRef; if (manglePackage) { @@ -287,7 +294,7 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, FileReference* newFile, FileReference* oldFile) -> bool { - mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{ + mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{ file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) }; return true; }; diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index e1be5d52e9cf..4539679fa769 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -20,6 +20,7 @@ #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "filter/ConfigFilter.h" #include "io/File.h" #include "process/IResourceTableConsumer.h" #include "util/Util.h" @@ -51,6 +52,11 @@ struct TableMergerOptions { * If true, resources in overlays can be added without previously having existed. */ bool autoAddOverlay = false; + + /** + * A filter that removes resources whose configurations don't match. + */ + IConfigFilter* filter = nullptr; }; /** diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index fa4afd3d852a..45c8c98780b8 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "filter/ConfigFilter.h" #include "io/FileSystem.h" #include "link/TableMerger.h" #include "test/Builders.h" @@ -243,4 +244,36 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { ASSERT_FALSE(merger.mergeOverlay({}, tableB.get())); } +TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) { + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = false; + + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("en")); + options.filter = &filter; + + test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml"); + const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main"); + const ConfigDescription configEn = test::parseConfigOrDie("en"); + const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR"); + + TableMerger merger(mContext.get(), &finalTable, options); + ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA)); + ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB)); + + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable, + u"@com.app.a:layout/main", + configEn)); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable, + u"@com.app.a:layout/main", + configFr)); + + EXPECT_NE(merger.getFilesToMerge().end(), + merger.getFilesToMerge().find(ResourceKeyRef(name, configEn))); + + EXPECT_EQ(merger.getFilesToMerge().end(), + merger.getFilesToMerge().find(ResourceKeyRef(name, configFr))); +} + } // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 93a11b9334a8..579a46ec230f 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -84,6 +84,11 @@ public: util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); } + ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path, + const ConfigDescription& config) { + return addValue(name, {}, config, + util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); + } ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) { |