diff options
164 files changed, 5254 insertions, 3192 deletions
diff --git a/Android.mk b/Android.mk index 7d942ebbeaa7..9d2ca0d50300 100644 --- a/Android.mk +++ b/Android.mk @@ -281,6 +281,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IAppOpsService.aidl \ core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \ core/java/com/android/internal/app/IBatteryStats.aidl \ + core/java/com/android/internal/app/IEphemeralResolver.aidl \ core/java/com/android/internal/app/IProcessStats.aidl \ core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \ core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \ diff --git a/api/current.txt b/api/current.txt index 7355fa6a0aca..8bac46dabe7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2733,6 +2733,7 @@ package android.accounts { method public abstract java.lang.String getAuthTokenLabel(java.lang.String); method public final android.os.IBinder getIBinder(); method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException; + method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry"; } @@ -2797,6 +2798,7 @@ package android.accounts { method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String); method public void setPassword(android.accounts.Account, java.lang.String); method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String); + method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator"; field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; @@ -2813,6 +2815,8 @@ package android.accounts { field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse"; field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse"; field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount"; + field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle"; + field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken"; field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType"; field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName"; field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types"; @@ -3496,6 +3500,7 @@ package android.app { method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); method public void openContextMenu(android.view.View); method public void openOptionsMenu(); + method public void overlayWithDecorCaption(boolean); method public void overridePendingTransition(int, int); method public void postponeEnterTransition(); method public void recreate(); @@ -3774,6 +3779,8 @@ package android.app { } public class ActivityOptions { + method public android.graphics.Rect getLaunchBounds(); + method public boolean hasLaunchBounds(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); @@ -3783,6 +3790,7 @@ package android.app { method public static android.app.ActivityOptions makeTaskLaunchBehind(); method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); method public void requestUsageTimeReport(android.app.PendingIntent); + method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time"; @@ -36286,6 +36294,7 @@ package android.view { method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(); method public void createContextMenu(android.view.ContextMenu); method public void destroyDrawingCache(); + method public final boolean didLayoutParamsChange(); method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets); method public void dispatchConfigurationChanged(android.content.res.Configuration); method public void dispatchDisplayHint(int); @@ -36511,6 +36520,7 @@ package android.view { method public boolean isOpaque(); method protected boolean isPaddingOffsetRequired(); method public boolean isPaddingRelative(); + method public final boolean isPartialLayoutRequested(); method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); @@ -37120,6 +37130,7 @@ package android.view { method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>); method protected boolean drawChild(android.graphics.Canvas, android.view.View, long); method public void endViewTransition(android.view.View); + method public int findDependentLayoutAxes(android.view.View, int); method public android.view.View focusSearch(android.view.View, int); method public void focusableViewAvailable(android.view.View); method public boolean gatherTransparentRegion(android.graphics.Region); @@ -37186,6 +37197,8 @@ package android.view { method public void requestChildFocus(android.view.View, android.view.View); method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean); method public void requestDisallowInterceptTouchEvent(boolean); + method public void requestLayoutForChild(android.view.View); + method public void requestPartialLayoutForChild(android.view.View); method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent); method public void requestTransparentRegion(android.view.View); method public void scheduleLayoutAnimation(); @@ -37300,6 +37313,7 @@ package android.view { method public abstract void childHasTransientStateChanged(android.view.View, boolean); method public abstract void clearChildFocus(android.view.View); method public abstract void createContextMenu(android.view.ContextMenu); + method public abstract int findDependentLayoutAxes(android.view.View, int); method public abstract android.view.View focusSearch(android.view.View, int); method public abstract void focusableViewAvailable(android.view.View); method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point); @@ -37329,12 +37343,16 @@ package android.view { method public abstract void requestDisallowInterceptTouchEvent(boolean); method public abstract void requestFitSystemWindows(); method public abstract void requestLayout(); + method public abstract void requestLayoutForChild(android.view.View); method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent); method public abstract void requestTransparentRegion(android.view.View); method public abstract boolean showContextMenuForChild(android.view.View); method public abstract boolean showContextMenuForChild(android.view.View, float, float); method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback); method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int); + field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3 + field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1 + field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2 } public class ViewPropertyAnimator { diff --git a/api/system-current.txt b/api/system-current.txt index b7204114f49c..3a4706b48eed 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2832,6 +2832,7 @@ package android.accounts { method public abstract java.lang.String getAuthTokenLabel(java.lang.String); method public final android.os.IBinder getIBinder(); method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException; + method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException; method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException; field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry"; } @@ -2896,6 +2897,7 @@ package android.accounts { method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String); method public void setPassword(android.accounts.Account, java.lang.String); method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String); + method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator"; field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; @@ -2912,6 +2914,8 @@ package android.accounts { field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse"; field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse"; field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount"; + field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle"; + field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken"; field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType"; field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName"; field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types"; @@ -3599,6 +3603,7 @@ package android.app { method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int); method public void openContextMenu(android.view.View); method public void openOptionsMenu(); + method public void overlayWithDecorCaption(boolean); method public void overridePendingTransition(int, int); method public void postponeEnterTransition(); method public void recreate(); @@ -3883,6 +3888,8 @@ package android.app { } public class ActivityOptions { + method public android.graphics.Rect getLaunchBounds(); + method public boolean hasLaunchBounds(); method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); @@ -3892,6 +3899,7 @@ package android.app { method public static android.app.ActivityOptions makeTaskLaunchBehind(); method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); method public void requestUsageTimeReport(android.app.PendingIntent); + method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time"; @@ -8516,6 +8524,7 @@ package android.content { field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED"; field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT"; field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT"; + field public static final java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE"; field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"; field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED"; @@ -8563,6 +8572,7 @@ package android.content { field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK"; field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT"; + field public static final java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN"; field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF"; field public static final java.lang.String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; @@ -38607,6 +38617,7 @@ package android.view { method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(); method public void createContextMenu(android.view.ContextMenu); method public void destroyDrawingCache(); + method public final boolean didLayoutParamsChange(); method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets); method public void dispatchConfigurationChanged(android.content.res.Configuration); method public void dispatchDisplayHint(int); @@ -38832,6 +38843,7 @@ package android.view { method public boolean isOpaque(); method protected boolean isPaddingOffsetRequired(); method public boolean isPaddingRelative(); + method public final boolean isPartialLayoutRequested(); method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); @@ -39441,6 +39453,7 @@ package android.view { method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>); method protected boolean drawChild(android.graphics.Canvas, android.view.View, long); method public void endViewTransition(android.view.View); + method public int findDependentLayoutAxes(android.view.View, int); method public android.view.View focusSearch(android.view.View, int); method public void focusableViewAvailable(android.view.View); method public boolean gatherTransparentRegion(android.graphics.Region); @@ -39507,6 +39520,8 @@ package android.view { method public void requestChildFocus(android.view.View, android.view.View); method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean); method public void requestDisallowInterceptTouchEvent(boolean); + method public void requestLayoutForChild(android.view.View); + method public void requestPartialLayoutForChild(android.view.View); method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent); method public void requestTransparentRegion(android.view.View); method public void scheduleLayoutAnimation(); @@ -39621,6 +39636,7 @@ package android.view { method public abstract void childHasTransientStateChanged(android.view.View, boolean); method public abstract void clearChildFocus(android.view.View); method public abstract void createContextMenu(android.view.ContextMenu); + method public abstract int findDependentLayoutAxes(android.view.View, int); method public abstract android.view.View focusSearch(android.view.View, int); method public abstract void focusableViewAvailable(android.view.View); method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point); @@ -39650,12 +39666,16 @@ package android.view { method public abstract void requestDisallowInterceptTouchEvent(boolean); method public abstract void requestFitSystemWindows(); method public abstract void requestLayout(); + method public abstract void requestLayoutForChild(android.view.View); method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent); method public abstract void requestTransparentRegion(android.view.View); method public abstract boolean showContextMenuForChild(android.view.View); method public abstract boolean showContextMenuForChild(android.view.View, float, float); method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback); method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int); + field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3 + field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1 + field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2 } public class ViewPropertyAnimator { diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 62e0919a3d84..daf01ec06d0e 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -65,6 +65,7 @@ import android.util.ArrayMap; import android.view.IWindowManager; import com.android.internal.os.BaseCommand; +import com.android.internal.util.HexDump; import com.android.internal.util.Preconditions; import java.io.BufferedReader; @@ -152,6 +153,7 @@ public class Am extends BaseCommand { " am to-app-uri [INTENT]\n" + " am switch-user <USER_ID>\n" + " am start-user <USER_ID>\n" + + " am unlock-user <USER_ID> [TOKEN_HEX]\n" + " am stop-user [-w] <USER_ID>\n" + " am stack start <DISPLAY_ID> <INTENT>\n" + " am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" + @@ -411,6 +413,8 @@ public class Am extends BaseCommand { runSwitchUser(); } else if (op.equals("start-user")) { runStartUserInBackground(); + } else if (op.equals("unlock-user")) { + runUnlockUser(); } else if (op.equals("stop-user")) { runStopUser(); } else if (op.equals("stack")) { @@ -1086,6 +1090,21 @@ public class Am extends BaseCommand { } } + private void runUnlockUser() throws Exception { + int userId = Integer.parseInt(nextArgRequired()); + String tokenHex = nextArg(); + byte[] token = null; + if (tokenHex != null) { + token = HexDump.hexStringToByteArray(tokenHex); + } + boolean success = mAm.unlockUser(userId, token); + if (success) { + System.out.println("Success: user unlocked"); + } else { + System.err.println("Error: could not unlock user"); + } + } + private static class StopUserCallback extends IStopUserCallback.Stub { private boolean mFinished = false; diff --git a/cmds/appops/Android.mk b/cmds/appops/Android.mk index 1e15204a221d..6801ce9cd3af 100644 --- a/cmds/appops/Android.mk +++ b/cmds/appops/Android.mk @@ -3,14 +3,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_MODULE := appops -include $(BUILD_JAVA_LIBRARY) - -include $(CLEAR_VARS) LOCAL_MODULE := appops LOCAL_SRC_FILES := appops LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MODULE_TAGS := optional include $(BUILD_PREBUILT) - diff --git a/cmds/appops/appops b/cmds/appops/appops index 407e5511ad75..25d20311aae2 100755 --- a/cmds/appops/appops +++ b/cmds/appops/appops @@ -1,5 +1 @@ -# Script to start "appwidget" on the device, which has a very rudimentary shell. -base=/system -export CLASSPATH=$base/framework/appops.jar -exec app_process $base/bin com.android.commands.appops.AppOpsCommand "$@" - +cmd appops $@ diff --git a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java deleted file mode 100644 index c9b9e5862a48..000000000000 --- a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java +++ /dev/null @@ -1,334 +0,0 @@ -/* -** Copyright 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. -*/ - -package com.android.commands.appops; - -import android.app.ActivityManager; -import android.app.ActivityThread; -import android.app.AppOpsManager; -import android.content.Context; -import android.content.pm.IPackageManager; -import android.os.ServiceManager; -import android.os.UserHandle; - -import android.util.TimeUtils; -import com.android.internal.app.IAppOpsService; -import com.android.internal.os.BaseCommand; - -import java.io.PrintStream; -import java.util.List; - -/** - * This class is a command line utility for manipulating AppOps permissions. - */ -public class AppOpsCommand extends BaseCommand { - - public static void main(String[] args) { - new AppOpsCommand().run(args); - } - - @Override - public void onShowUsage(PrintStream out) { - out.println("usage: appops set [--user <USER_ID>] <PACKAGE> <OP> <MODE>\n" - + " appops get [--user <USER_ID>] <PACKAGE> [<OP>]\n" - + " appops reset [--user <USER_ID>] [<PACKAGE>]\n" - + " <PACKAGE> an Android package name.\n" - + " <OP> an AppOps operation.\n" - + " <MODE> one of allow, ignore, deny, or default\n" - + " <USER_ID> the user id under which the package is installed. If --user is not\n" - + " specified, the current user is assumed.\n"); - } - - private static final String COMMAND_SET = "set"; - private static final String COMMAND_GET = "get"; - private static final String COMMAND_RESET = "reset"; - - @Override - public void onRun() throws Exception { - String command = nextArgRequired(); - switch (command) { - case COMMAND_SET: - runSet(); - break; - - case COMMAND_GET: - runGet(); - break; - - case COMMAND_RESET: - runReset(); - break; - - default: - System.err.println("Error: Unknown command: '" + command + "'."); - break; - } - } - - private static final String ARGUMENT_USER = "--user"; - - // Modes - private static final String MODE_ALLOW = "allow"; - private static final String MODE_DENY = "deny"; - private static final String MODE_IGNORE = "ignore"; - private static final String MODE_DEFAULT = "default"; - - private int strOpToOp(String op) { - try { - return AppOpsManager.strOpToOp(op); - } catch (IllegalArgumentException e) { - } - try { - return Integer.parseInt(op); - } catch (NumberFormatException e) { - } - try { - return AppOpsManager.strDebugOpToOp(op); - } catch (IllegalArgumentException e) { - System.err.println("Error: " + e.getMessage()); - return -1; - } - } - - private void runSet() throws Exception { - String packageName = null; - String op = null; - String mode = null; - int userId = UserHandle.USER_CURRENT; - for (String argument; (argument = nextArg()) != null;) { - if (ARGUMENT_USER.equals(argument)) { - userId = Integer.parseInt(nextArgRequired()); - } else { - if (packageName == null) { - packageName = argument; - } else if (op == null) { - op = argument; - } else if (mode == null) { - mode = argument; - } else { - System.err.println("Error: Unsupported argument: " + argument); - return; - } - } - } - - if (packageName == null) { - System.err.println("Error: Package name not specified."); - return; - } else if (op == null) { - System.err.println("Error: Operation not specified."); - return; - } else if (mode == null) { - System.err.println("Error: Mode not specified."); - return; - } - - final int opInt = strOpToOp(op); - if (opInt < 0) { - return; - } - final int modeInt; - switch (mode) { - case MODE_ALLOW: - modeInt = AppOpsManager.MODE_ALLOWED; - break; - case MODE_DENY: - modeInt = AppOpsManager.MODE_ERRORED; - break; - case MODE_IGNORE: - modeInt = AppOpsManager.MODE_IGNORED; - break; - case MODE_DEFAULT: - modeInt = AppOpsManager.MODE_DEFAULT; - break; - default: - System.err.println("Error: Mode " + mode + " is not valid,"); - return; - } - - // Parsing complete, let's execute the command. - - if (userId == UserHandle.USER_CURRENT) { - userId = ActivityManager.getCurrentUser(); - } - - final IPackageManager pm = ActivityThread.getPackageManager(); - final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); - final int uid; - if ("root".equals(packageName)) { - uid = 0; - } else { - uid = pm.getPackageUid(packageName, userId); - } - if (uid < 0) { - System.err.println("Error: No UID for " + packageName + " in user " + userId); - return; - } - appOpsService.setMode(opInt, uid, packageName, modeInt); - } - - private void runGet() throws Exception { - String packageName = null; - String op = null; - int userId = UserHandle.USER_CURRENT; - for (String argument; (argument = nextArg()) != null;) { - if (ARGUMENT_USER.equals(argument)) { - userId = Integer.parseInt(nextArgRequired()); - } else { - if (packageName == null) { - packageName = argument; - } else if (op == null) { - op = argument; - } else { - System.err.println("Error: Unsupported argument: " + argument); - return; - } - } - } - - if (packageName == null) { - System.err.println("Error: Package name not specified."); - return; - } - - final int opInt = op != null ? strOpToOp(op) : 0; - - // Parsing complete, let's execute the command. - - if (userId == UserHandle.USER_CURRENT) { - userId = ActivityManager.getCurrentUser(); - } - - final IPackageManager pm = ActivityThread.getPackageManager(); - final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); - final int uid; - if ("root".equals(packageName)) { - uid = 0; - } else { - uid = pm.getPackageUid(packageName, userId); - } - if (uid < 0) { - System.err.println("Error: No UID for " + packageName + " in user " + userId); - return; - } - List<AppOpsManager.PackageOps> ops = appOpsService.getOpsForPackage(uid, packageName, - op != null ? new int[] {opInt} : null); - if (ops == null || ops.size() <= 0) { - System.out.println("No operations."); - return; - } - final long now = System.currentTimeMillis(); - for (int i=0; i<ops.size(); i++) { - List<AppOpsManager.OpEntry> entries = ops.get(i).getOps(); - for (int j=0; j<entries.size(); j++) { - AppOpsManager.OpEntry ent = entries.get(j); - System.out.print(AppOpsManager.opToName(ent.getOp())); - System.out.print(": "); - switch (ent.getMode()) { - case AppOpsManager.MODE_ALLOWED: - System.out.print("allow"); - break; - case AppOpsManager.MODE_IGNORED: - System.out.print("ignore"); - break; - case AppOpsManager.MODE_ERRORED: - System.out.print("deny"); - break; - case AppOpsManager.MODE_DEFAULT: - System.out.print("default"); - break; - default: - System.out.print("mode="); - System.out.print(ent.getMode()); - break; - } - if (ent.getTime() != 0) { - System.out.print("; time="); - StringBuilder sb = new StringBuilder(); - TimeUtils.formatDuration(now - ent.getTime(), sb); - System.out.print(sb); - System.out.print(" ago"); - } - if (ent.getRejectTime() != 0) { - System.out.print("; rejectTime="); - StringBuilder sb = new StringBuilder(); - TimeUtils.formatDuration(now - ent.getRejectTime(), sb); - System.out.print(sb); - System.out.print(" ago"); - } - if (ent.getDuration() == -1) { - System.out.print(" (running)"); - } else if (ent.getDuration() != 0) { - System.out.print("; duration="); - StringBuilder sb = new StringBuilder(); - TimeUtils.formatDuration(ent.getDuration(), sb); - System.out.print(sb); - } - System.out.println(); - } - } - } - - private void runReset() throws Exception { - String packageName = null; - int userId = UserHandle.USER_CURRENT; - for (String argument; (argument = nextArg()) != null;) { - if (ARGUMENT_USER.equals(argument)) { - String userStr = nextArgRequired(); - if ("all".equals(userStr)) { - userId = UserHandle.USER_ALL; - } else if ("current".equals(userStr)) { - userId = UserHandle.USER_CURRENT; - } else if ("owner".equals(userStr) || "system".equals(userStr)) { - userId = UserHandle.USER_SYSTEM; - } else { - userId = Integer.parseInt(nextArgRequired()); - } - } else { - if (packageName == null) { - packageName = argument; - } else { - System.err.println("Error: Unsupported argument: " + argument); - return; - } - } - } - - // Parsing complete, let's execute the command. - - if (userId == UserHandle.USER_CURRENT) { - userId = ActivityManager.getCurrentUser(); - } - - final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); - appOpsService.resetAllModes(userId, packageName); - System.out.print("Reset all modes for: "); - if (userId == UserHandle.USER_ALL) { - System.out.print("all users"); - } else { - System.out.print("user "); System.out.print(userId); - } - System.out.print(", "); - if (packageName == null) { - System.out.println("all packages"); - } else { - System.out.print("package "); System.out.println(packageName); - } - } -} diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java index a675769a7f04..726167e07b42 100644 --- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java +++ b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java @@ -291,7 +291,7 @@ public final class SettingsCmd { System.err.println(" settings [--user NUM] delete namespace key"); System.err.println(" settings [--user NUM] list namespace"); System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive"); - System.err.println("If '--user NUM' is not given, the operations are performed on the" + System.err.println("If '--user NUM' is not given, the operations are performed on the " + "system user."); } diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index 9c401c7f6e51..185ceb430e13 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -116,6 +116,25 @@ public abstract class AbstractAccountAuthenticator { */ public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry"; + /** + * Bundle key used for the {@link String} account type in session bundle. + * This is used in the default implementation of + * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession. + */ + private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.KEY_AUTH_TOKEN_TYPE"; + /** + * Bundle key used for the {@link String} array of required features in + * session bundle. This is used in the default implementation of + * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession. + */ + private static final String KEY_REQUIRED_FEATURES = "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES"; + /** + * Bundle key used for the {@link Bundle} options in session bundle. This is + * used in default implementation of {@link #startAddAccountSession}. TODO: + * and startUpdateCredentialsSession. + */ + private static final String KEY_OPTIONS = "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS"; + private final Context mContext; public AbstractAccountAuthenticator(Context context) { @@ -336,6 +355,36 @@ public abstract class AbstractAccountAuthenticator { handleException(response, "addAccountFromCredentials", account.toString(), e); } } + + @Override + public void startAddAccountSession(IAccountAuthenticatorResponse response, + String accountType, String authTokenType, String[] features, Bundle options) + throws RemoteException { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "startAddAccountSession: accountType " + accountType + + ", authTokenType " + authTokenType + + ", features " + (features == null ? "[]" : Arrays.toString(features))); + } + checkBinderPermission(); + try { + final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession( + new AccountAuthenticatorResponse(response), accountType, authTokenType, + features, options); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + if (result != null) { + result.keySet(); // force it to be unparcelled + } + Log.v(TAG, "startAddAccountSession: result " + + AccountManager.sanitizeResult(result)); + } + if (result != null) { + response.onResult(result); + } + } catch (Exception e) { + handleException(response, "startAddAccountSession", accountType, e); + } + } } private void handleException(IAccountAuthenticatorResponse response, String method, @@ -603,4 +652,52 @@ public abstract class AbstractAccountAuthenticator { }).start(); return null; } + + /** + * Starts the add account session to authenticate user to an account of the + * specified accountType. + * + * @param response to send the result back to the AccountManager, will never + * be null + * @param accountType the type of account to authenticate with, will never + * be null + * @param authTokenType the type of auth token to retrieve after + * authenticating with the account, may be null + * @param requiredFeatures a String array of authenticator-specific features + * that the account authenticated with must support, may be null + * @param options a Bundle of authenticator-specific options, may be null + * @return a Bundle result or null if the result is to be returned via the + * response. The result will contain either: + * <ul> + * <li>{@link AccountManager#KEY_INTENT}, or + * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding + * the account to device later, and if account is authenticated, + * optional {@link AccountManager#KEY_PASSWORD} and + * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the + * status of the account, or + * <li>{@link AccountManager#KEY_ERROR_CODE} and + * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error + * </ul> + * @throws NetworkErrorException if the authenticator could not honor the + * request due to a network error + */ + public Bundle startAddAccountSession(final AccountAuthenticatorResponse response, + final String accountType, final String authTokenType, final String[] requiredFeatures, + final Bundle options) + throws NetworkErrorException { + new Thread(new Runnable() { + @Override + public void run() { + Bundle sessionBundle = new Bundle(); + sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); + sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures); + sessionBundle.putBundle(KEY_OPTIONS, options); + Bundle result = new Bundle(); + result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); + response.onResult(result); + } + + }).start(); + return null; + } } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 0a7568a8c876..42e5e2a5fb6e 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -240,6 +240,20 @@ public class AccountManager { */ public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure"; + /** + * Bundle key used for a {@link Bundle} in result from + * {@link #startAddAccountSession} and friends which returns session data + * for installing an account later. + */ + public static final String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle"; + + /** + * Bundle key used for the {@link String} account status token in result + * from {@link #startAddAccountSession} and friends which returns + * information about a particular account. + */ + public static final String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken"; + public static final String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator"; public static final String AUTHENTICATOR_META_DATA_NAME = @@ -2590,4 +2604,84 @@ public class AccountManager { } } } + + /** + * Asks the user to authenticate with an account of a specified type. The + * authenticator for this account type processes this request with the + * appropriate user interface. If the user does elect to authenticate with a + * new account, a bundle of session data for installing the account later is + * returned with optional account password and account status token. + * <p> + * This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * <p> + * <p> + * <b>NOTE:</b> The account will not be installed to the device by calling + * this api alone. + * + * @param accountType The type of account to add; must not be null + * @param authTokenType The type of auth token (see {@link #getAuthToken}) + * this account will need to be able to generate, null for none + * @param requiredFeatures The features (see {@link #hasFeatures}) this + * account must have, null for none + * @param options Authenticator-specific options for the request, may be + * null or empty + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to + * create an account; used only to call startActivity(); if null, + * the prompt will not be launched directly, but the necessary + * {@link Intent} will be returned to the caller instead + * @param callback Callback to invoke when the request completes, null for + * no callback + * @param handler {@link Handler} identifying the callback thread, null for + * the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * these fields if activity was specified and user was authenticated + * with an account: + * <ul> + * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for + * adding the the to the device later. + * <li>{@link #KEY_PASSWORD} - optional, the password or password + * hash of the account. + * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check + * status of the account + * </ul> + * If no activity was specified, the returned Bundle contains only + * {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * actual account creation process. If authenticator doesn't support + * this method, the returned Bundle contains only + * {@link #KEY_ACCOUNT_SESSION_BUNDLE} with encrypted + * {@code options} needed to add account later. If an error + * occurred, {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li>{@link AuthenticatorException} if no authenticator was + * registered for this account type or the authenticator failed to + * respond + * <li>{@link OperationCanceledException} if the operation was + * canceled for any reason, including the user canceling the + * creation process or adding accounts (of this type) has been + * disabled by policy + * <li>{@link IOException} if the authenticator experienced an I/O + * problem creating a new account, usually because of network + * trouble + * </ul> + */ + public AccountManagerFuture<Bundle> startAddAccountSession(final String accountType, + final String authTokenType, final String[] requiredFeatures, final Bundle options, + final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + final Bundle optionsIn = new Bundle(); + if (options != null) { + optionsIn.putAll(options); + } + optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); + + return new AmsTask(activity, handler, callback) { + @Override + public void doWork() throws RemoteException { + mService.startAddAccountSession(mResponse, accountType, authTokenType, + requiredFeatures, activity != null, optionsIn); + } + }.start(); + } } diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl index 58612da063dc..b326070ddfc7 100644 --- a/core/java/android/accounts/IAccountAuthenticator.aidl +++ b/core/java/android/accounts/IAccountAuthenticator.aidl @@ -83,4 +83,11 @@ oneway interface IAccountAuthenticator { */ void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account, in Bundle accountCredentials); + + /** + * Starts the add account session by prompting the user for account information + * and return a Bundle containing data to finish the session later. + */ + void startAddAccountSession(in IAccountAuthenticatorResponse response, String accountType, + String authTokenType, in String[] requiredFeatures, in Bundle options); } diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 0d95db1d8302..5de311ea6ec7 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -83,4 +83,9 @@ interface IAccountManager { String getPreviousName(in Account account); boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId); + /* Add account in two steps. */ + void startAddAccountSession(in IAccountManagerResponse response, String accountType, + String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch, + in Bundle options); + } diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index d8d27376165c..20d71a62835c 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -250,50 +250,19 @@ public class AnimatorInflater { /** * PathDataEvaluator is used to interpolate between two paths which are * represented in the same format but different control points' values. - * The path is represented as an array of PathDataNode here, which is - * fundamentally an array of floating point numbers. + * The path is represented as verbs and points for each of the verbs. */ - private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> { - private PathParser.PathDataNode[] mNodeArray; - - /** - * Create a PathParser.PathDataNode[] that does not reuse the animated value. - * Care must be taken when using this option because on every evaluation - * a new <code>PathParser.PathDataNode[]</code> will be allocated. - */ - private PathDataEvaluator() {} - - /** - * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call. - * Caution must be taken to ensure that the value returned from - * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or - * used across threads. The value will be modified on each <code>evaluate()</code> call. - * - * @param nodeArray The array to modify and return from <code>evaluate</code>. - */ - public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) { - mNodeArray = nodeArray; - } + private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> { + private final PathParser.PathData mPathData = new PathParser.PathData(); @Override - public PathParser.PathDataNode[] evaluate(float fraction, - PathParser.PathDataNode[] startPathData, - PathParser.PathDataNode[] endPathData) { - if (!PathParser.canMorph(startPathData, endPathData)) { + public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData, + PathParser.PathData endPathData) { + if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) { throw new IllegalArgumentException("Can't interpolate between" + " two incompatible pathData"); } - - if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) { - mNodeArray = PathParser.deepCopyNodes(startPathData); - } - - for (int i = 0; i < startPathData.length; i++) { - mNodeArray[i].interpolatePathDataNode(startPathData[i], - endPathData[i], fraction); - } - - return mNodeArray; + return mPathData; } } @@ -323,13 +292,14 @@ public class AnimatorInflater { if (valueType == VALUE_TYPE_PATH) { String fromString = styledAttributes.getString(valueFromId); String toString = styledAttributes.getString(valueToId); - PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); - PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); + PathParser.PathData nodesFrom = fromString == null + ? null : new PathParser.PathData(fromString); + PathParser.PathData nodesTo = toString == null + ? null : new PathParser.PathData(toString); if (nodesFrom != null || nodesTo != null) { if (nodesFrom != null) { - TypeEvaluator evaluator = - new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); + TypeEvaluator evaluator = new PathDataEvaluator(); if (nodesTo != null) { if (!PathParser.canMorph(nodesFrom, nodesTo)) { throw new InflateException(" Can't morph from " + fromString + " to " + @@ -342,8 +312,7 @@ public class AnimatorInflater { (Object) nodesFrom); } } else if (nodesTo != null) { - TypeEvaluator evaluator = - new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + TypeEvaluator evaluator = new PathDataEvaluator(); returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator, (Object) nodesTo); } @@ -484,23 +453,25 @@ public class AnimatorInflater { TypeEvaluator evaluator = null; String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom); String toString = arrayAnimator.getString(R.styleable.Animator_valueTo); - PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString); - PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString); - - if (nodesFrom != null) { - if (nodesTo != null) { - anim.setObjectValues(nodesFrom, nodesTo); - if (!PathParser.canMorph(nodesFrom, nodesTo)) { + PathParser.PathData pathDataFrom = fromString == null + ? null : new PathParser.PathData(fromString); + PathParser.PathData pathDataTo = toString == null + ? null : new PathParser.PathData(toString); + + if (pathDataFrom != null) { + if (pathDataTo != null) { + anim.setObjectValues(pathDataFrom, pathDataTo); + if (!PathParser.canMorph(pathDataFrom, pathDataTo)) { throw new InflateException(arrayAnimator.getPositionDescription() + " Can't morph from " + fromString + " to " + toString); } } else { - anim.setObjectValues((Object)nodesFrom); + anim.setObjectValues((Object)pathDataFrom); } - evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom)); - } else if (nodesTo != null) { - anim.setObjectValues((Object)nodesTo); - evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo)); + evaluator = new PathDataEvaluator(); + } else if (pathDataTo != null) { + anim.setObjectValues((Object)pathDataTo); + evaluator = new PathDataEvaluator(); } if (DBG_ANIMATOR_INFLATER && evaluator != null) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 472d97fdb9ed..8bb0ff5a2781 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6618,6 +6618,17 @@ public class Activity extends ContextThemeWrapper } /** + * Set whether the caption should displayed directly on the content rather than push it down. + * + * This affects only freeform windows since they display the caption and only the main + * window of the activity. The caption is used to drag the window around and also shows + * maximize and close action buttons. + */ + public void overlayWithDecorCaption(boolean overlay) { + mWindow.setOverlayDecorCaption(overlay); + } + + /** * Interface for informing a translucent {@link Activity} once all visible activities below it * have completed drawing. This is necessary only after an {@link Activity} has been made * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 00bba2db111b..f7aee759f3b9 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -528,6 +528,15 @@ public class ActivityManager { return stackId == FULLSCREEN_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID; } + + /** + * Returns true if animation specs should be constructed for app transition that moves + * the task to the specified stack. + */ + public static boolean useAnimationSpecForAppTransition(int stackId) { + return stackId == FREEFORM_WORKSPACE_STACK_ID + || stackId == FULLSCREEN_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID; + } } /** @@ -884,7 +893,7 @@ public class ActivityManager { if (mIcon != null) { return mIcon; } - return loadTaskDescriptionIcon(mIconFilename); + return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId()); } /** @hide */ @@ -898,11 +907,11 @@ public class ActivityManager { } /** @hide */ - public static Bitmap loadTaskDescriptionIcon(String iconFilename) { + public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) { if (iconFilename != null) { try { return ActivityManagerNative.getDefault(). - getTaskDescriptionIcon(iconFilename); + getTaskDescriptionIcon(iconFilename, userId); } catch (RemoteException e) { } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index e246e620dc72..0b7b6fc83ae2 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1962,6 +1962,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case UNLOCK_USER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int userId = data.readInt(); + byte[] token = data.createByteArray(); + boolean result = unlockUser(userId, token); + reply.writeNoException(); + reply.writeInt(result ? 1 : 0); + return true; + } + case STOP_USER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int userid = data.readInt(); @@ -2490,7 +2500,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case GET_TASK_DESCRIPTION_ICON_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String filename = data.readString(); - Bitmap icon = getTaskDescriptionIcon(filename); + int userId = data.readInt(); + Bitmap icon = getTaskDescriptionIcon(filename, userId); reply.writeNoException(); if (icon == null) { reply.writeInt(0); @@ -5249,6 +5260,20 @@ class ActivityManagerProxy implements IActivityManager return result; } + public boolean unlockUser(int userId, byte[] token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(userId); + data.writeByteArray(token); + mRemote.transact(IActivityManager.UNLOCK_USER_TRANSACTION, data, reply, 0); + reply.readException(); + boolean result = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return result; + } + public int stopUser(int userid, IStopUserCallback callback) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -5998,11 +6023,12 @@ class ActivityManagerProxy implements IActivityManager } @Override - public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException { + public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeString(filename); + data.writeInt(userId); mRemote.transact(GET_TASK_DESCRIPTION_ICON_TRANSACTION, data, reply, 0); reply.readException(); final Bitmap icon = reply.readInt() == 0 ? null : Bitmap.CREATOR.createFromParcel(reply); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 57900aa4eb89..cee1aa5b90e9 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -64,11 +64,13 @@ public class ActivityOptions { public static final String KEY_PACKAGE_NAME = "android:activity.packageName"; /** - * The bounds that the activity should be started in. Set to null explicitly - * for full screen. If the key is not found, previous bounds will be preserved. + * The bounds (window size) that the activity should be launched in. Set to null explicitly for + * full screen. If the key is not found, previous bounds will be preserved. + * NOTE: This value is ignored on devices that don't have + * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled. * @hide */ - public static final String KEY_BOUNDS = "android:activity.bounds"; + public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds"; /** * Type of animation that arguments specify. @@ -193,8 +195,8 @@ public class ActivityOptions { public static final int ANIM_CLIP_REVEAL = 11; private String mPackageName; - private boolean mHasBounds; - private Rect mBounds; + private boolean mHasLaunchBounds; + private Rect mLaunchBounds; private int mAnimationType = ANIM_NONE; private int mCustomEnterResId; private int mCustomExitResId; @@ -705,9 +707,9 @@ public class ActivityOptions { } catch (RuntimeException e) { Slog.w(TAG, e); } - mHasBounds = opts.containsKey(KEY_BOUNDS); - if (mHasBounds) { - mBounds = opts.getParcelable(KEY_BOUNDS); + mHasLaunchBounds = opts.containsKey(KEY_LAUNCH_BOUNDS); + if (mHasLaunchBounds) { + mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS); } mAnimationType = opts.getInt(KEY_ANIM_TYPE); switch (mAnimationType) { @@ -766,10 +768,15 @@ public class ActivityOptions { } } - /** @hide */ - public ActivityOptions setBounds(Rect bounds) { - mHasBounds = true; - mBounds = bounds; + /** + * Sets the bounds (window size) that the activity should be launched in. Set to null explicitly + * for full screen. + * NOTE: This value is ignored on devices that don't have + * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled. + */ + public ActivityOptions setLaunchBounds(Rect launchBounds) { + mHasLaunchBounds = true; + mLaunchBounds = launchBounds; return this; } @@ -778,14 +785,12 @@ public class ActivityOptions { return mPackageName; } - /** @hide */ - public boolean hasBounds() { - return mHasBounds; + public boolean hasLaunchBounds() { + return mHasLaunchBounds; } - /** @hide */ - public Rect getBounds(){ - return mBounds; + public Rect getLaunchBounds(){ + return mLaunchBounds; } /** @hide */ @@ -997,8 +1002,8 @@ public class ActivityOptions { if (mPackageName != null) { b.putString(KEY_PACKAGE_NAME, mPackageName); } - if (mHasBounds) { - b.putParcelable(KEY_BOUNDS, mBounds); + if (mHasLaunchBounds) { + b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds); } b.putInt(KEY_ANIM_TYPE, mAnimationType); if (mUsageTimeReport != null) { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b3d6382524fb..802880dc7ebc 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -78,6 +78,7 @@ import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.security.NetworkSecurityPolicy; +import android.security.net.config.NetworkSecurityConfigProvider; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -4831,6 +4832,11 @@ public final class ActivityThread { } } + // 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 + // the provider is installed. + NetworkSecurityConfigProvider.install(appContext); + // Continue loading instrumentation. if (ii != null) { final ApplicationInfo instrApp = new ApplicationInfo(); diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index c0cc56678f3b..20f34951e363 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -575,14 +575,20 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { setGhostVisibility(View.INVISIBLE); mHasStopped = true; mIsCanceled = true; + clearState(); + return super.cancelPendingTransitions(); + } + + @Override + protected void clearState() { + mSharedElementsBundle = null; + mEnterViewsTransition = null; mResultReceiver = null; if (mBackgroundAnimator != null) { mBackgroundAnimator.cancel(); mBackgroundAnimator = null; } - mActivity = null; - clearState(); - return super.cancelPendingTransitions(); + super.clearState(); } private void makeOpaque() { diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 4b670cd52f00..e93b40ed7210 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -470,6 +470,11 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { mActivity = null; } // Clear the state so that we can't hold any references accidentally and leak memory. + clearState(); + } + + @Override + protected void clearState() { mHandler = null; mSharedElementBundle = null; if (mBackgroundAnimator != null) { @@ -477,7 +482,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { mBackgroundAnimator = null; } mExitSharedElementBundle = null; - clearState(); + super.clearState(); } @Override diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 3d0fc92a2603..db4f5c15920d 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -390,6 +390,7 @@ public interface IActivityManager extends IInterface { // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; public boolean startUserInBackground(int userid) throws RemoteException; + public boolean unlockUser(int userid, byte[] token) throws RemoteException; public int stopUser(int userid, IStopUserCallback callback) throws RemoteException; public UserInfo getCurrentUser() throws RemoteException; public boolean isUserRunning(int userid, int flags) throws RemoteException; @@ -497,7 +498,7 @@ public interface IActivityManager extends IInterface { public void resizeTask(int taskId, Rect bounds, int resizeMode) throws RemoteException; public Rect getTaskBounds(int taskId) throws RemoteException; - public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException; + public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException; public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) throws RemoteException; @@ -904,4 +905,5 @@ public interface IActivityManager extends IInterface { int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348; int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349; int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350; + int UNLOCK_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 351; } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 30232da96e90..84ddd9f0cf7f 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -50,9 +50,6 @@ interface INotificationManager void setPackagePriority(String pkg, int uid, int priority); int getPackagePriority(String pkg, int uid); - void setPackagePeekable(String pkg, int uid, boolean peekable); - boolean getPackagePeekable(String pkg, int uid); - void setPackageVisibilityOverride(String pkg, int uid, int visibility); int getPackageVisibilityOverride(String pkg, int uid); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 390c280e6b8d..4e6548b7bb17 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -838,13 +838,6 @@ public class Notification implements Parcelable public static final String EXTRA_PEOPLE = "android.people"; /** - * {@link #extras} key: used to provide hints about the appropriateness of - * displaying this notification as a heads-up notification. - * @hide - */ - public static final String EXTRA_AS_HEADS_UP = "headsup"; - - /** * Allow certain system-generated notifications to appear before the device is provisioned. * Only available to notifications coming from the android package. * @hide @@ -887,32 +880,6 @@ public class Notification implements Parcelable */ public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; - /** - * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be - * displayed in the heads up space. - * - * <p> - * If this notification has a {@link #fullScreenIntent}, then it will always launch the - * full-screen intent when posted. - * </p> - * @hide - */ - public static final int HEADS_UP_NEVER = 0; - - /** - * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be - * displayed as a heads up. - * @hide - */ - public static final int HEADS_UP_ALLOWED = 1; - - /** - * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a - * good candidate for display as a heads up. - * @hide - */ - public static final int HEADS_UP_REQUESTED = 2; - private Icon mSmallIcon; private Icon mLargeIcon; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 30fe531c5186..9d941fd12d1e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1426,6 +1426,36 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE"; /** + * Activity Action: Launch ephemeral installer. + * <p> + * Input: The data must be a http: URI that the ephemeral application is registered + * to handle. + * <p class="note"> + * This is a protected intent that can only be sent by the system. + * </p> + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE + = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; + + /** + * Service Action: Resolve ephemeral application. + * <p> + * The system will have a persistent connection to this service. + * This is a protected intent that can only be sent by the system. + * </p> + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE + = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; + + /** * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a * package. Specifies the installer package name; this package will receive the * {@link #ACTION_APP_ERROR} intent. diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index eda4136b311c..1996e0feb401 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -982,7 +982,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { .getAbsolutePath(); if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0 - && SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) { + && StorageManager.isFileBasedEncryptionEnabled()) { dataDir = deviceEncryptedDataDir; } else { dataDir = credentialEncryptedDataDir; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 566de4e04cb9..42fef3ba5aa0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -240,16 +240,15 @@ public abstract class PackageManager { public static final int GET_ENCRYPTION_UNAWARE_COMPONENTS = 0x00040000; /** - * {@link PackageInfo} flag: return components as if the given user is - * running with amnesia. This typically limits the component to only those - * marked as {@link ComponentInfo#encryptionAware}, unless + * {@link PackageInfo} flag: return components that are marked as + * {@link ComponentInfo#encryptionAware}, unless * {@link #GET_ENCRYPTION_UNAWARE_COMPONENTS} is also specified. * <p> * This flag is for internal use only. * * @hide */ - public static final int FLAG_USER_RUNNING_WITH_AMNESIA = 0x00080000; + public static final int MATCH_ENCRYPTION_AWARE_ONLY = 0x00080000; /** * Flag for {@link addCrossProfileIntentFilter}: if this flag is set: diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 2e43ffcbcc4f..c6510f0d1d79 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -1301,23 +1301,6 @@ public interface IMountService extends IInterface { } @Override - public boolean isPerUserEncryptionEnabled() throws RemoteException { - Parcel _data = Parcel.obtain(); - Parcel _reply = Parcel.obtain(); - boolean _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - mRemote.transact(Stub.TRANSACTION_isPerUserEncryptionEnabled, _data, _reply, 0); - _reply.readException(); - _result = 0 != _reply.readInt(); - } finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - - @Override public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -1459,7 +1442,6 @@ public interface IMountService extends IInterface { static final int TRANSACTION_prepareUserStorage = IBinder.FIRST_CALL_TRANSACTION + 66; - static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67; static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68; static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69; @@ -2074,13 +2056,6 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } - case TRANSACTION_isPerUserEncryptionEnabled: { - data.enforceInterface(DESCRIPTOR); - boolean result = isPerUserEncryptionEnabled(); - reply.writeNoException(); - reply.writeInt(result ? 1 : 0); - return true; - } case TRANSACTION_mountAppFuse: { data.enforceInterface(DESCRIPTOR); String name = data.readString(); @@ -2411,7 +2386,5 @@ public interface IMountService extends IInterface { public void prepareUserStorage(String volumeUuid, int userId, int serialNumber) throws RemoteException; - public boolean isPerUserEncryptionEnabled() throws RemoteException; - public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 2d9090b6f99a..db1256409cb7 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -33,6 +33,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -77,11 +78,9 @@ public class StorageManager { /** {@hide} */ public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable"; /** {@hide} */ - public static final String PROP_HAS_FBE = "vold.has_fbe"; - /** {@hide} */ public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable"; /** {@hide} */ - public static final String PROP_EMULATE_FBE = "vold.emulate_fbe"; + public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe"; /** {@hide} */ public static final String UUID_PRIVATE_INTERNAL = null; @@ -1021,12 +1020,9 @@ public class StorageManager { } /** {@hide} */ - public boolean isPerUserEncryptionEnabled() { - try { - return mMountService.isPerUserEncryptionEnabled(); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } + public static boolean isFileBasedEncryptionEnabled() { + return "file".equals(SystemProperties.get("ro.crypto.type", "none")) + || SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false); } /** {@hide} */ diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index c368e5a24a1b..5997515e58d2 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -435,7 +435,7 @@ public class VolumeInfo implements Parcelable { return null; } - final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); + final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(uri); intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true); diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 159ca01d3f85..af7f472eed9d 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -125,8 +125,7 @@ public final class DocumentsContract { public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; /** {@hide} */ - public static final String - ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT"; + public static final String ACTION_BROWSE = "android.provider.action.BROWSE"; /** {@hide} */ public static final String diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java index f09947904478..f17a16c066f0 100644 --- a/core/java/android/util/PathParser.java +++ b/core/java/android/util/PathParser.java @@ -15,10 +15,6 @@ package android.util; import android.graphics.Path; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; /** * @hide @@ -45,663 +41,94 @@ public class PathParser { } /** - * @param pathData The string representing a path, the same as "d" string in svg file. - * @return an array of the PathDataNode. - */ - public static PathDataNode[] createNodesFromPathData(String pathData) { - if (pathData == null) { - return null; - } - int start = 0; - int end = 1; - - ArrayList<PathDataNode> list = new ArrayList<PathDataNode>(); - while (end < pathData.length()) { - end = nextStart(pathData, end); - String s = pathData.substring(start, end).trim(); - if (s.length() > 0) { - float[] val = getFloats(s); - addNode(list, s.charAt(0), val); - } - - start = end; - end++; - } - if ((end - start) == 1 && start < pathData.length()) { - addNode(list, pathData.charAt(start), new float[0]); - } - return list.toArray(new PathDataNode[list.size()]); - } - - /** - * @param source The array of PathDataNode to be duplicated. - * @return a deep copy of the <code>source</code>. - */ - public static PathDataNode[] deepCopyNodes(PathDataNode[] source) { - if (source == null) { - return null; - } - PathDataNode[] copy = new PathParser.PathDataNode[source.length]; - for (int i = 0; i < source.length; i ++) { - copy[i] = new PathDataNode(source[i]); - } - return copy; - } - - /** - * @param nodesFrom The source path represented in an array of PathDataNode - * @param nodesTo The target path represented in an array of PathDataNode - * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code> - */ - public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) { - if (nodesFrom == null || nodesTo == null) { - return false; - } - - if (nodesFrom.length != nodesTo.length) { - return false; - } - - for (int i = 0; i < nodesFrom.length; i ++) { - if (nodesFrom[i].mType != nodesTo[i].mType - || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) { - return false; - } - } - return true; - } - - /** - * Update the target's data to match the source. - * Before calling this, make sure canMorph(target, source) is true. + * Interpret PathData as path commands and insert the commands to the given path. * - * @param target The target path represented in an array of PathDataNode - * @param source The source path represented in an array of PathDataNode + * @param data The source PathData to be converted. + * @param outPath The Path object where path commands will be inserted. */ - public static void updateNodes(PathDataNode[] target, PathDataNode[] source) { - for (int i = 0; i < source.length; i ++) { - target[i].mType = source[i].mType; - for (int j = 0; j < source[i].mParams.length; j ++) { - target[i].mParams[j] = source[i].mParams[j]; - } - } - } - - private static int nextStart(String s, int end) { - char c; - - while (end < s.length()) { - c = s.charAt(end); - // Note that 'e' or 'E' are not valid path commands, but could be - // used for floating point numbers' scientific notation. - // Therefore, when searching for next command, we should ignore 'e' - // and 'E'. - if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) - && c != 'e' && c != 'E') { - return end; - } - end++; - } - return end; - } - - private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) { - list.add(new PathDataNode(cmd, val)); - } - - private static class ExtractFloatResult { - // We need to return the position of the next separator and whether the - // next float starts with a '-' or a '.'. - int mEndPosition; - boolean mEndWithNegOrDot; + public static void createPathFromPathData(Path outPath, PathData data) { + nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData); } /** - * Parse the floats in the string. - * This is an optimized version of parseFloat(s.split(",|\\s")); - * - * @param s the string containing a command and list of floats - * @return array of floats - */ - private static float[] getFloats(String s) { - if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') { - return new float[0]; - } - try { - float[] results = new float[s.length()]; - int count = 0; - int startPosition = 1; - int endPosition = 0; - - ExtractFloatResult result = new ExtractFloatResult(); - int totalLength = s.length(); - - // The startPosition should always be the first character of the - // current number, and endPosition is the character after the current - // number. - while (startPosition < totalLength) { - extract(s, startPosition, result); - endPosition = result.mEndPosition; - - if (startPosition < endPosition) { - results[count++] = Float.parseFloat( - s.substring(startPosition, endPosition)); - } - - if (result.mEndWithNegOrDot) { - // Keep the '-' or '.' sign with next number. - startPosition = endPosition; - } else { - startPosition = endPosition + 1; - } - } - return Arrays.copyOf(results, count); - } catch (NumberFormatException e) { - throw new RuntimeException("error in parsing \"" + s + "\"", e); - } - } - - /** - * Calculate the position of the next comma or space or negative sign - * @param s the string to search - * @param start the position to start searching - * @param result the result of the extraction, including the position of the - * the starting position of next number, whether it is ending with a '-'. + * @param pathDataFrom The source path represented in PathData + * @param pathDataTo The target path represented in PathData + * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code> */ - private static void extract(String s, int start, ExtractFloatResult result) { - // Now looking for ' ', ',', '.' or '-' from the start. - int currentIndex = start; - boolean foundSeparator = false; - result.mEndWithNegOrDot = false; - boolean secondDot = false; - boolean isExponential = false; - for (; currentIndex < s.length(); currentIndex++) { - boolean isPrevExponential = isExponential; - isExponential = false; - char currentChar = s.charAt(currentIndex); - switch (currentChar) { - case ' ': - case ',': - foundSeparator = true; - break; - case '-': - // The negative sign following a 'e' or 'E' is not a separator. - if (currentIndex != start && !isPrevExponential) { - foundSeparator = true; - result.mEndWithNegOrDot = true; - } - break; - case '.': - if (!secondDot) { - secondDot = true; - } else { - // This is the second dot, and it is considered as a separator. - foundSeparator = true; - result.mEndWithNegOrDot = true; - } - break; - case 'e': - case 'E': - isExponential = true; - break; - } - if (foundSeparator) { - break; - } - } - // When there is nothing found, then we put the end position to the end - // of the string. - result.mEndPosition = currentIndex; + public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) { + return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData); } /** - * Each PathDataNode represents one command in the "d" attribute of the svg - * file. - * An array of PathDataNode can represent the whole "d" attribute. + * PathData class is a wrapper around the native PathData object, which contains + * the result of parsing a path string. Specifically, there are verbs and points + * associated with each verb stored in PathData. This data can then be used to + * generate commands to manipulate a Path. */ - public static class PathDataNode { - private char mType; - private float[] mParams; - - private PathDataNode(char type, float[] params) { - mType = type; - mParams = params; + public static class PathData { + long mNativePathData = 0; + public PathData() { + mNativePathData = nCreateEmptyPathData(); } - private PathDataNode(PathDataNode n) { - mType = n.mType; - mParams = Arrays.copyOf(n.mParams, n.mParams.length); + public PathData(PathData data) { + mNativePathData = nCreatePathData(data.mNativePathData); } - /** - * Convert an array of PathDataNode to Path. - * - * @param node The source array of PathDataNode. - * @param path The target Path object. - */ - public static void nodesToPath(PathDataNode[] node, Path path) { - float[] current = new float[6]; - char previousCommand = 'm'; - for (int i = 0; i < node.length; i++) { - addCommand(path, current, previousCommand, node[i].mType, node[i].mParams); - previousCommand = node[i].mType; + public PathData(String pathString) { + mNativePathData = nCreatePathDataFromString(pathString, pathString.length()); + if (mNativePathData == 0) { + throw new IllegalArgumentException("Invalid pathData: " + pathString); } } /** - * The current PathDataNode will be interpolated between the - * <code>nodeFrom</code> and <code>nodeTo</code> according to the - * <code>fraction</code>. + * Update the path data to match the source. + * Before calling this, make sure canMorph(target, source) is true. * - * @param nodeFrom The start value as a PathDataNode. - * @param nodeTo The end value as a PathDataNode - * @param fraction The fraction to interpolate. + * @param source The source path represented in PathData */ - public void interpolatePathDataNode(PathDataNode nodeFrom, - PathDataNode nodeTo, float fraction) { - for (int i = 0; i < nodeFrom.mParams.length; i++) { - mParams[i] = nodeFrom.mParams[i] * (1 - fraction) - + nodeTo.mParams[i] * fraction; - } + public void setPathData(PathData source) { + nSetPathData(mNativePathData, source.mNativePathData); } - private static void addCommand(Path path, float[] current, - char previousCmd, char cmd, float[] val) { - - int incr = 2; - float currentX = current[0]; - float currentY = current[1]; - float ctrlPointX = current[2]; - float ctrlPointY = current[3]; - float currentSegmentStartX = current[4]; - float currentSegmentStartY = current[5]; - float reflectiveCtrlPointX; - float reflectiveCtrlPointY; - - switch (cmd) { - case 'z': - case 'Z': - path.close(); - // Path is closed here, but we need to move the pen to the - // closed position. So we cache the segment's starting position, - // and restore it here. - currentX = currentSegmentStartX; - currentY = currentSegmentStartY; - ctrlPointX = currentSegmentStartX; - ctrlPointY = currentSegmentStartY; - path.moveTo(currentX, currentY); - break; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - incr = 2; - break; - case 'h': - case 'H': - case 'v': - case 'V': - incr = 1; - break; - case 'c': - case 'C': - incr = 6; - break; - case 's': - case 'S': - case 'q': - case 'Q': - incr = 4; - break; - case 'a': - case 'A': - incr = 7; - break; - } - - for (int k = 0; k < val.length; k += incr) { - switch (cmd) { - case 'm': // moveto - Start a new sub-path (relative) - currentX += val[k + 0]; - currentY += val[k + 1]; - if (k > 0) { - // According to the spec, if a moveto is followed by multiple - // pairs of coordinates, the subsequent pairs are treated as - // implicit lineto commands. - path.rLineTo(val[k + 0], val[k + 1]); - } else { - path.rMoveTo(val[k + 0], val[k + 1]); - currentSegmentStartX = currentX; - currentSegmentStartY = currentY; - } - break; - case 'M': // moveto - Start a new sub-path - currentX = val[k + 0]; - currentY = val[k + 1]; - if (k > 0) { - // According to the spec, if a moveto is followed by multiple - // pairs of coordinates, the subsequent pairs are treated as - // implicit lineto commands. - path.lineTo(val[k + 0], val[k + 1]); - } else { - path.moveTo(val[k + 0], val[k + 1]); - currentSegmentStartX = currentX; - currentSegmentStartY = currentY; - } - break; - case 'l': // lineto - Draw a line from the current point (relative) - path.rLineTo(val[k + 0], val[k + 1]); - currentX += val[k + 0]; - currentY += val[k + 1]; - break; - case 'L': // lineto - Draw a line from the current point - path.lineTo(val[k + 0], val[k + 1]); - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'h': // horizontal lineto - Draws a horizontal line (relative) - path.rLineTo(val[k + 0], 0); - currentX += val[k + 0]; - break; - case 'H': // horizontal lineto - Draws a horizontal line - path.lineTo(val[k + 0], currentY); - currentX = val[k + 0]; - break; - case 'v': // vertical lineto - Draws a vertical line from the current point (r) - path.rLineTo(0, val[k + 0]); - currentY += val[k + 0]; - break; - case 'V': // vertical lineto - Draws a vertical line from the current point - path.lineTo(currentX, val[k + 0]); - currentY = val[k + 0]; - break; - case 'c': // curveto - Draws a cubic Bézier curve (relative) - path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], - val[k + 4], val[k + 5]); - - ctrlPointX = currentX + val[k + 2]; - ctrlPointY = currentY + val[k + 3]; - currentX += val[k + 4]; - currentY += val[k + 5]; - - break; - case 'C': // curveto - Draws a cubic Bézier curve - path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3], - val[k + 4], val[k + 5]); - currentX = val[k + 4]; - currentY = val[k + 5]; - ctrlPointX = val[k + 2]; - ctrlPointY = val[k + 3]; - break; - case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) - reflectiveCtrlPointX = 0; - reflectiveCtrlPointY = 0; - if (previousCmd == 'c' || previousCmd == 's' - || previousCmd == 'C' || previousCmd == 'S') { - reflectiveCtrlPointX = currentX - ctrlPointX; - reflectiveCtrlPointY = currentY - ctrlPointY; - } - path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1], - val[k + 2], val[k + 3]); - - ctrlPointX = currentX + val[k + 0]; - ctrlPointY = currentY + val[k + 1]; - currentX += val[k + 2]; - currentY += val[k + 3]; - break; - case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) - reflectiveCtrlPointX = currentX; - reflectiveCtrlPointY = currentY; - if (previousCmd == 'c' || previousCmd == 's' - || previousCmd == 'C' || previousCmd == 'S') { - reflectiveCtrlPointX = 2 * currentX - ctrlPointX; - reflectiveCtrlPointY = 2 * currentY - ctrlPointY; - } - path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = val[k + 0]; - ctrlPointY = val[k + 1]; - currentX = val[k + 2]; - currentY = val[k + 3]; - break; - case 'q': // Draws a quadratic Bézier (relative) - path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = currentX + val[k + 0]; - ctrlPointY = currentY + val[k + 1]; - currentX += val[k + 2]; - currentY += val[k + 3]; - break; - case 'Q': // Draws a quadratic Bézier - path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]); - ctrlPointX = val[k + 0]; - ctrlPointY = val[k + 1]; - currentX = val[k + 2]; - currentY = val[k + 3]; - break; - case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) - reflectiveCtrlPointX = 0; - reflectiveCtrlPointY = 0; - if (previousCmd == 'q' || previousCmd == 't' - || previousCmd == 'Q' || previousCmd == 'T') { - reflectiveCtrlPointX = currentX - ctrlPointX; - reflectiveCtrlPointY = currentY - ctrlPointY; - } - path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1]); - ctrlPointX = currentX + reflectiveCtrlPointX; - ctrlPointY = currentY + reflectiveCtrlPointY; - currentX += val[k + 0]; - currentY += val[k + 1]; - break; - case 'T': // Draws a quadratic Bézier curve (reflective control point) - reflectiveCtrlPointX = currentX; - reflectiveCtrlPointY = currentY; - if (previousCmd == 'q' || previousCmd == 't' - || previousCmd == 'Q' || previousCmd == 'T') { - reflectiveCtrlPointX = 2 * currentX - ctrlPointX; - reflectiveCtrlPointY = 2 * currentY - ctrlPointY; - } - path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - val[k + 0], val[k + 1]); - ctrlPointX = reflectiveCtrlPointX; - ctrlPointY = reflectiveCtrlPointY; - currentX = val[k + 0]; - currentY = val[k + 1]; - break; - case 'a': // Draws an elliptical arc - // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) - drawArc(path, - currentX, - currentY, - val[k + 5] + currentX, - val[k + 6] + currentY, - val[k + 0], - val[k + 1], - val[k + 2], - val[k + 3] != 0, - val[k + 4] != 0); - currentX += val[k + 5]; - currentY += val[k + 6]; - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - case 'A': // Draws an elliptical arc - drawArc(path, - currentX, - currentY, - val[k + 5], - val[k + 6], - val[k + 0], - val[k + 1], - val[k + 2], - val[k + 3] != 0, - val[k + 4] != 0); - currentX = val[k + 5]; - currentY = val[k + 6]; - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - } - previousCmd = cmd; + @Override + protected void finalize() throws Throwable { + if (mNativePathData != 0) { + nFinalize(mNativePathData); + mNativePathData = 0; } - current[0] = currentX; - current[1] = currentY; - current[2] = ctrlPointX; - current[3] = ctrlPointY; - current[4] = currentSegmentStartX; - current[5] = currentSegmentStartY; + super.finalize(); } + } - private static void drawArc(Path p, - float x0, - float y0, - float x1, - float y1, - float a, - float b, - float theta, - boolean isMoreThanHalf, - boolean isPositiveArc) { - - /* Convert rotation angle from degrees to radians */ - double thetaD = Math.toRadians(theta); - /* Pre-compute rotation matrix entries */ - double cosTheta = Math.cos(thetaD); - double sinTheta = Math.sin(thetaD); - /* Transform (x0, y0) and (x1, y1) into unit space */ - /* using (inverse) rotation, followed by (inverse) scale */ - double x0p = (x0 * cosTheta + y0 * sinTheta) / a; - double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; - double x1p = (x1 * cosTheta + y1 * sinTheta) / a; - double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; - - /* Compute differences and averages */ - double dx = x0p - x1p; - double dy = y0p - y1p; - double xm = (x0p + x1p) / 2; - double ym = (y0p + y1p) / 2; - /* Solve for intersecting unit circles */ - double dsq = dx * dx + dy * dy; - if (dsq == 0.0) { - Log.w(LOGTAG, " Points are coincident"); - return; /* Points are coincident */ - } - double disc = 1.0 / dsq - 1.0 / 4.0; - if (disc < 0.0) { - Log.w(LOGTAG, "Points are too far apart " + dsq); - float adjust = (float) (Math.sqrt(dsq) / 1.99999); - drawArc(p, x0, y0, x1, y1, a * adjust, - b * adjust, theta, isMoreThanHalf, isPositiveArc); - return; /* Points are too far apart */ - } - double s = Math.sqrt(disc); - double sdx = s * dx; - double sdy = s * dy; - double cx; - double cy; - if (isMoreThanHalf == isPositiveArc) { - cx = xm - sdy; - cy = ym + sdx; - } else { - cx = xm + sdy; - cy = ym - sdx; - } - - double eta0 = Math.atan2((y0p - cy), (x0p - cx)); - - double eta1 = Math.atan2((y1p - cy), (x1p - cx)); - - double sweep = (eta1 - eta0); - if (isPositiveArc != (sweep >= 0)) { - if (sweep > 0) { - sweep -= 2 * Math.PI; - } else { - sweep += 2 * Math.PI; - } - } - - cx *= a; - cy *= b; - double tcx = cx; - cx = cx * cosTheta - cy * sinTheta; - cy = tcx * sinTheta + cy * cosTheta; - - arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); - } - - /** - * Converts an arc to cubic Bezier segments and records them in p. - * - * @param p The target for the cubic Bezier segments - * @param cx The x coordinate center of the ellipse - * @param cy The y coordinate center of the ellipse - * @param a The radius of the ellipse in the horizontal direction - * @param b The radius of the ellipse in the vertical direction - * @param e1x E(eta1) x coordinate of the starting point of the arc - * @param e1y E(eta2) y coordinate of the starting point of the arc - * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane - * @param start The start angle of the arc on the ellipse - * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse - */ - private static void arcToBezier(Path p, - double cx, - double cy, - double a, - double b, - double e1x, - double e1y, - double theta, - double start, - double sweep) { - // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html - // and http://www.spaceroots.org/documents/ellipse/node22.html - - // Maximum of 45 degrees per cubic Bezier segment - int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI)); - - double eta1 = start; - double cosTheta = Math.cos(theta); - double sinTheta = Math.sin(theta); - double cosEta1 = Math.cos(eta1); - double sinEta1 = Math.sin(eta1); - double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); - double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); - - double anglePerSegment = sweep / numSegments; - for (int i = 0; i < numSegments; i++) { - double eta2 = eta1 + anglePerSegment; - double sinEta2 = Math.sin(eta2); - double cosEta2 = Math.cos(eta2); - double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); - double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); - double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; - double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; - double tanDiff2 = Math.tan((eta2 - eta1) / 2); - double alpha = - Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; - double q1x = e1x + alpha * ep1x; - double q1y = e1y + alpha * ep1y; - double q2x = e2x - alpha * ep2x; - double q2y = e2y - alpha * ep2y; - - p.cubicTo((float) q1x, - (float) q1y, - (float) q2x, - (float) q2y, - (float) e2x, - (float) e2y); - eta1 = eta2; - e1x = e2x; - e1y = e2y; - ep1x = ep2x; - ep1y = ep2y; - } - } + /** + * Interpolate between the <code>fromData</code> and <code>toData</code> according to the + * <code>fraction</code>, and put the resulting path data into <code>outData</code>. + * + * @param outData The resulting PathData of the interpolation + * @param fromData The start value as a PathData. + * @param toData The end value as a PathData + * @param fraction The fraction to interpolate. + */ + public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData, + float fraction) { + return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData, + toData.mNativePathData, fraction); } + // Native functions are defined below. private static native boolean nParseStringForPath(long pathPtr, String pathString, int stringLength); + private static native void nCreatePathFromPathData(long outPathPtr, long pathData); + private static native long nCreateEmptyPathData(); + private static native long nCreatePathData(long nativePtr); + private static native long nCreatePathDataFromString(String pathString, int stringLength); + private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr, + long toDataPtr, float fraction); + private static native void nFinalize(long nativePtr); + private static native boolean nCanMorph(long fromDataPtr, long toDataPtr); + private static native void nSetPathData(long outDataPtr, long fromDataPtr); } + + diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 30408c6d4d46..2415b4dd7557 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -83,6 +83,7 @@ import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.AccessibilityIterators.CharacterTextSegmentIterator; import android.view.AccessibilityIterators.WordTextSegmentIterator; import android.view.AccessibilityIterators.ParagraphTextSegmentIterator; +import android.view.ViewGroup.LayoutParams; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; @@ -2416,6 +2417,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_SCROLL_INDICATOR_END * 1 PFLAG3_ASSIST_BLOCKED * 1111111 PFLAG3_POINTER_ICON_MASK + * 1 PFLAG3_PARTIAL_LAYOUT_REQUESTED + * 1 PFLAG3_LAYOUT_PARAMS_CHANGED * |-------|-------|-------|-------| */ @@ -2504,6 +2507,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000; + /* End of masks for mPrivateFlags3 */ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; @@ -2642,6 +2646,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT; /** + * Flag indicating that this view has requested a partial layout and + * is added to the AttachInfo's list of views that need a partial layout + * request handled on the next traversal. + */ + static final int PFLAG3_PARTIAL_LAYOUT_REQUESTED = 0x800000; + + /** + * Flag indicating that this view's LayoutParams have been explicitly changed + * since the last layout pass. + */ + static final int PFLAG3_LAYOUT_PARAMS_CHANGED = 0x1000000; + + /** * Always allow a user to over-scroll this view, provided it is a * view that can scroll. * @@ -12622,10 +12639,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * ViewGroup.LayoutParams, and these correspond to the different subclasses * of ViewGroup that are responsible for arranging their children. * - * This method may return null if this View is not attached to a parent + * <p>This method may return null if this View is not attached to a parent * ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)} * was not invoked successfully. When a View is attached to a parent - * ViewGroup, this method must not return null. + * ViewGroup, this method must not return null.</p> + * + * <p>Callers that modify the returned LayoutParams object should call + * {@link #setLayoutParams(LayoutParams)} to explicitly inform the view that + * LayoutParams have changed.</p> * * @return The LayoutParams associated with this view, or null if no * parameters have been set yet @@ -12642,6 +12663,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * correspond to the different subclasses of ViewGroup that are responsible * for arranging their children. * + * <p>If the View's existing LayoutParams object as obtained by {@link #getLayoutParams()} is + * modified, you should call this method to inform the view that it has changed.</p> + * * @param params The layout parameters for this view, cannot be null */ public void setLayoutParams(ViewGroup.LayoutParams params) { @@ -12649,6 +12673,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, throw new NullPointerException("Layout parameters cannot be null"); } mLayoutParams = params; + mPrivateFlags3 |= PFLAG3_LAYOUT_PARAMS_CHANGED; resolveLayoutParams(); if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).onSetLayoutParams(this, params); @@ -14324,7 +14349,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mParent.requestTransparentRegion(this); } - mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT; + if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) { + initialAwakenScrollBars(); + mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH; + } + + mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED); jumpDrawablesToCurrentState(); @@ -14662,8 +14692,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper protected void onDetachedFromWindowInternal() { + if (mAttachInfo != null && isPartialLayoutRequested()) { + mAttachInfo.mPartialLayoutViews.remove(this); + } + mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; - mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT; + mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED + | PFLAG3_LAYOUT_PARAMS_CHANGED); removeUnsetPressCallback(); removeLongPressCallback(); @@ -16850,6 +16885,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Indicates whether or not this view has requested a partial layout that + * may not affect its size or position within its parent. This state will be reset + * the next time this view is laid out. + * + * @return true if partial layout has been requested + */ + public final boolean isPartialLayoutRequested() { + return (mPrivateFlags3 & PFLAG3_PARTIAL_LAYOUT_REQUESTED) + == PFLAG3_PARTIAL_LAYOUT_REQUESTED; + } + + /** + * Returns true if this view's {@link ViewGroup.LayoutParams LayoutParams} changed + * since the last time this view was successfully laid out. Typically this happens as a + * result of a call to {@link #setLayoutParams(LayoutParams)}. + * + * @return true if this view's LayoutParams changed since last layout. + */ + public final boolean didLayoutParamsChange() { + return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED; + } + + /** * Return true if o is a ViewGroup that is laying out using optical bounds. * @hide */ @@ -16906,6 +16964,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; + mPrivateFlags3 &= ~PFLAG3_LAYOUT_PARAMS_CHANGED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { @@ -16919,6 +16978,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; + mPrivateFlags3 &= ~PFLAG3_PARTIAL_LAYOUT_REQUESTED; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; } @@ -19012,7 +19072,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { - mParent.requestLayout(); + mParent.requestLayoutForChild(this); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; @@ -19031,6 +19091,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags |= PFLAG_INVALIDATED; } + void forcePartialLayout() { + forceLayout(); + mPrivateFlags3 |= PFLAG3_PARTIAL_LAYOUT_REQUESTED; + } + /** * <p> * This is called to find out how big a view should be. The parent @@ -21867,6 +21932,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, interface Callbacks { void playSoundEffect(int effectId); boolean performHapticFeedback(int effectId, boolean always); + void schedulePartialLayout(); } /** @@ -22242,6 +22308,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, IBinder mDragToken; /** + * Used to track views that need (at least) a partial relayout at their current size + * during the next traversal. + */ + final List<View> mPartialLayoutViews = new ArrayList<View>(); + + /** * Creates a new set of attachment information with the specified * events handler and thread. * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 25df00483acd..6812fd14ff69 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -60,6 +60,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * <p> @@ -5517,6 +5519,172 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager int l, int t, int r, int b); /** + * {@inheritDoc} + * + * <p>Most subclasses should not need to override this method. The default implementation + * will call {@link #findDependentLayoutAxes(View, int)} to determine how + * to optimally proceed. If neither horizontal nor vertical layout depends on the given + * child, this method will call {@link #requestPartialLayoutForChild(View)}. If one or both + * do, it will call {@link #requestLayout()}.</p> + * + * @param child Child requesting a layout + */ + @Override + public void requestLayoutForChild(View child) { + if (child == null || child.getParent() != this) { + throw new IllegalArgumentException( + "child parameter must be a direct child view of this ViewGroup"); + } + + // If we don't have a parent ourselves, record that we need a full layout. + // Our whole subtree is detached. + final ViewParent parent = getParent(); + if (parent == null) { + requestLayout(); + return; + } + + // We can optimize the layout request for this child into a partial layout + // if the child has already been laid out at least once and neither horizontal nor + // vertical layout within ourselves is dependent on pending layout changes within + // this child. Otherwise we need to request a full layout for ourselves and continue + // to recurse up the view hierarchy. + if (child.isLaidOut() && findDependentLayoutAxes(child, FLAG_LAYOUT_AXIS_ANY) == 0) { + requestPartialLayoutForChild(child); + } else { + requestLayout(); + } + } + + /** + * {@inheritDoc} + * + * <p>The default implementation returns {@link #FLAG_LAYOUT_AXIS_ANY}. + * Optimized implementations for specific ViewGroup subclasses may check if the child's + * {@link View#didLayoutParamsChange() LayoutParams changed} and in what ways.</p> + * + * @param child Direct child of this ViewParent to check + * @param axisFilter Which axes to check for dependencies. Can be + * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL} + * or {@link #FLAG_LAYOUT_AXIS_ANY}. + * @return Axes of this ViewParent that depend on the given child's layout changes + */ + @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + return FLAG_LAYOUT_AXIS_ANY; + } + + /** + * This is a helper implementation for {@link #findDependentLayoutAxes(View, int)} that + * is not the default implementation in ViewGroup. This is to preserve compatibility with + * existing app-side ViewGroup subclasses that existed before the partial layout system was + * added to Android. It explicitly checks that the LayoutParams of the child are of the + * expected type so that subclasses of standard framework layouts do not erroneously + * start believing that it's safe to do a partial layout when that assertion can't + * reasonably be confirmed. + * + * <p>If you're reading this as an author of a custom ViewGroup's findDependentLayoutAxes + * method you might be frustrated to discover that it is not a part of the Android public API. + * Many ViewGroup implementations will need to make small but important modifications + * to an implementation like this one in order to be correct. Instead of encouraging + * view authors to call this method, then make their own redundant recursive calls to + * <code>getParent().findDependentLayoutAxes(...)</code> in addition to the one + * that can happen here, this method is hidden and only used internally.</p> + * + * <p>Do feel free to copy this implementation and adapt it to suit your own purposes.</p> + * + * @hide + */ + protected final int findDependentLayoutAxesHelper(View child, int axisFilter, + Class<?> layoutParamsClass) { + if (!checkPartialLayoutParams(child, layoutParamsClass)) return axisFilter; + if (child.didLayoutParamsChange()) { + // Anything could have changed about our previous assumptions. + return axisFilter; + } + + final LayoutParams lp = child.getLayoutParams(); + + // Our layout can always end up depending on a WRAP_CONTENT child. + final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0) + | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter; + + if (wrapAxisFilter == axisFilter) { + // We know all queried axes are affected, just return early. + return wrapAxisFilter; + } + + // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine + // that our layout will remain stable within our parent. We need to ask. + final int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0) + | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter; + + if (matchAxisFilter != 0) { + final ViewParent parent = getParent(); + if (parent != null) { + // If our parent depends on us for an axis, then our layout can also be affected + // by a MATCH_PARENT child along that axis. + return getParent().findDependentLayoutAxes(this, matchAxisFilter) + | wrapAxisFilter; + } + + // If we don't have a parent, assume we're affected + // in any determined affected direction. + return matchAxisFilter | wrapAxisFilter; + } + + // Two exact sizes and LayoutParams didn't change. We're safe. + return 0; + } + + /** + * Throw an IllegalArgumentException if the supplied view is not a direct child of + * this ViewGroup and return false if this view's LayoutParams is not of class lpClass. + * Implementations of {@link ViewGroup#findDependentLayoutAxes(View, int)} use this + * to check input parameters and defensively return the full axis filter mask themselves + * if the LayoutParams class is not of the exact expected type; e.g. it is a subclass + * of one of the standard framework layouts and we can't make assumptions. + * @hide + */ + protected final boolean checkPartialLayoutParams(View child, Class<?> lpClass) { + if (child.getParent() != this) { + throw new IllegalArgumentException("View " + child + + " is not a direct child of " + this); + } + final ViewGroup.LayoutParams lp = child.getLayoutParams(); + return lp != null || lp.getClass() == lpClass; + } + + /** + * Called when a child of this ViewParent requires a relayout before the next frame + * is drawn, but the caller can guarantee that the size of the child will not change + * during a measure and layout pass. + * + * <p>A call to this method will schedule a partial layout for the supplied view as long as + * it is a direct child of this ViewGroup and this ViewGroup is attached to a window. + * On the next scheduled view hierarchy traversal the given child view will be re-measured + * at its current measured size and re-laid out at its current position within its parent.</p> + * + * @param child Child that requires a partial layout + */ + public void requestPartialLayoutForChild(View child) { + if (!child.isPartialLayoutRequested()) { + child.forcePartialLayout(); + if (mAttachInfo != null) { + final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews; + final boolean schedule = partialLayoutViews.isEmpty(); + partialLayoutViews.add(child); + if (schedule) { + mAttachInfo.mRootCallbacks.schedulePartialLayout(); + } + child.invalidate(); + } else { + requestLayout(); + } + } + } + + /** * Indicates whether the view group has the ability to animate its children * after the first layout. * @@ -5862,7 +6030,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * of its descendants */ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return p; + return new LayoutParams(p); } /** diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 07f1e2cbdb4f..6ae448a7e888 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -26,6 +26,11 @@ import android.view.accessibility.AccessibilityEvent; * */ public interface ViewParent { + public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; + public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; + public static final int FLAG_LAYOUT_AXIS_ANY + = FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL; + /** * Called when something has changed which has invalidated the layout of a * child of this view parent. This will schedule a layout pass of the view @@ -601,4 +606,48 @@ public interface ViewParent { * @return true if the action was consumed by this ViewParent */ public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments); + + /** + * Called when a child of this ViewParent requires a relayout before + * the next frame is drawn. A call to {@link View#requestLayout() child.requestLayout()} + * will implicitly result in a call to + * <code>child.getParent().requestLayoutForChild(child)</code>. App code should not call this + * method directly. Call <code>child.requestLayout()</code> instead. + * + * <p>On versions of Android from API 23 and older, a call to {@link View#requestLayout()} + * would cause a matching call to <code>requestLayout</code> on each parent view up to + * the root. With the addition of <code>requestLayoutForChild</code> a view's parent may + * explicitly decide how to handle a layout request. This allows for optimizations when + * a view parent knows that a layout-altering change in a child will not affect its own + * measurement.</p> + * + * @param child Child requesting a layout + */ + public void requestLayoutForChild(View child); + + /** + * Determine which axes of this ViewParent's layout are dependent on the given + * direct child view. The returned value is a flag set that may contain + * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL} and/or {@link #FLAG_LAYOUT_AXIS_VERTICAL}. + * {@link #FLAG_LAYOUT_AXIS_ANY} is provided as a shortcut for + * <code>FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL</code>. + * + * <p>The given child must be a direct child view. Implementations should throw + * {@link IllegalArgumentException} otherwise.</p> + * + * <p>The caller may specify which axes it cares about. This should be treated as a filter. + * Implementations should never return a result that would be different from + * <code>result & axisFilter</code>.</p> + * + * @param child Direct child of this ViewParent to check + * @param axisFilter Which axes to check for dependencies. Can be + * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL} + * or {@link #FLAG_LAYOUT_AXIS_ANY}. + * @return Axes of this ViewParent that depend on the given child's layout changes + * + * @see #FLAG_LAYOUT_AXIS_HORIZONTAL + * @see #FLAG_LAYOUT_AXIS_VERTICAL + * @see #FLAG_LAYOUT_AXIS_ANY + */ + public int findDependentLayoutAxes(View child, int axisFilter); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f1d9f1ab1b45..2c0cd7a61af0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -91,6 +91,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.HashSet; +import java.util.List; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -952,6 +953,25 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void requestLayoutForChild(View child) { + requestLayout(); + } + + @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + if (child != mView) { + return 0; + } + + final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) child.getLayoutParams(); + final int horizontal = (lp.width == WindowManager.LayoutParams.WRAP_CONTENT + || lp.horizontalWeight != 0) ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0; + final int vertical = (lp.height == WindowManager.LayoutParams.WRAP_CONTENT + || lp.verticalWeight != 0) ? FLAG_LAYOUT_AXIS_VERTICAL : 0; + return (horizontal | vertical) & axisFilter; + } + + @Override public boolean isLayoutRequested() { return mLayoutRequested; } @@ -1095,6 +1115,10 @@ public final class ViewRootImpl implements ViewParent, } } + public void schedulePartialLayout() { + scheduleTraversals(); + } + /** * Notifies the HardwareRenderer that a new frame will be coming soon. * Currently only {@link ThreadedRenderer} cares about this, and uses @@ -1934,7 +1958,48 @@ public final class ViewRootImpl implements ViewParent, || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); + } + + /* + * Handle partial layouts. + * + * Views that have requested partial layouts will not change size or position + * within their parent view, therefore we will re-measure and re-layout each one + * after any regularly scheduled layout pass. Any view that already had its + * isLayoutRequested bit cleared will be skipped, since this means the view has already + * been measured and laid out on this traversal pass naturally. Views won't be added + * to this list if layout was already requested when a partial layout is requested + * for a view, so there should not be duplicates in the list. + */ + final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews; + final boolean didPartialLayout; + if (!partialLayoutViews.isEmpty()) { + final int count = partialLayoutViews.size(); + mInLayout = true; + for (int i = 0; i < count; i++) { + final View view = partialLayoutViews.get(i); + + // Make sure the view is still attached and that it still has layout requested. + // We might have already serviced the layout request through the standard full-tree + // layout pass above or even through a previous partial layout view in this list. + if (view.isAttachedToWindow() && view.isLayoutRequested()) { + final int widthSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), + MeasureSpec.EXACTLY); + final int heightSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), + MeasureSpec.EXACTLY); + view.measure(widthSpec, heightSpec); + view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + } + } + mInLayout = false; + partialLayoutViews.clear(); + didPartialLayout = true; + triggerGlobalLayoutListener = true; + } else { + didPartialLayout = false; + } + if (didLayout || didPartialLayout) { // By this point all views have been sized and positioned // We can compute the transparent area @@ -1964,7 +2029,7 @@ public final class ViewRootImpl implements ViewParent, if (DBG) { System.out.println("======================================"); - System.out.println("performTraversals -- after setFrame"); + System.out.println("performTraversals -- after performLayout/partial layout"); host.debug(); } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 825937289233..3c4d45a244b9 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -276,6 +276,8 @@ public abstract class Window { private boolean mDestroyed; + private boolean mOverlayWithDecorCaption = false; + // The current window attributes. private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); @@ -2044,4 +2046,18 @@ public abstract class Window { /** @hide */ public void setTheme(int resId) { } + + /** + * Whether the caption should be displayed directly on the content rather than push the content + * down. This affects only freeform windows since they display the caption. + * @hide + */ + public void setOverlayDecorCaption(boolean overlayCaption) { + mOverlayWithDecorCaption = overlayCaption; + } + + /** @hide */ + public boolean getOverlayDecorCaption() { + return mOverlayWithDecorCaption; + } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 9428f44296b4..19a98f345498 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -540,6 +540,13 @@ public final class InputMethodManager { void deactivate() { mActive = false; } + + @Override + public String toString() { + return "ControlledInputConnectionWrapper{mActive=" + mActive + + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive + + "}"; + } } final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index b8faf0c6cf98..90de0539df92 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2109,6 +2109,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mSelector == null) { useDefaultSelector(); diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index a018d2644eab..9f94005ff778 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -28,6 +28,8 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; import android.util.AttributeSet; import android.view.Gravity; import android.view.RemotableViewMethod; @@ -447,6 +449,68 @@ public class CheckedTextView extends TextView implements Checkable { return CheckedTextView.class.getName(); } + static class SavedState extends BaseSavedState { + boolean checked; + + /** + * Constructor called from {@link CheckedTextView#onSaveInstanceState()} + */ + SavedState(Parcelable superState) { + super(superState); + } + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(Parcel in) { + super(in); + checked = (Boolean)in.readValue(null); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeValue(checked); + } + + @Override + public String toString() { + return "CheckedTextView.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " checked=" + checked + "}"; + } + + public static final Parcelable.Creator<SavedState> CREATOR + = new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + + SavedState ss = new SavedState(superState); + + ss.checked = isChecked(); + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + + super.onRestoreInstanceState(ss.getSuperState()); + setChecked(ss.checked); + requestLayout(); + } + /** @hide */ @Override public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 280ff151cd6d..4d9f55c2c074 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -21,12 +21,8 @@ import java.util.ArrayList; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.PorterDuff; import android.graphics.Rect; -import android.graphics.Region; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; @@ -36,9 +32,6 @@ import android.view.ViewGroup; import android.view.ViewHierarchyEncoder; import android.widget.RemoteViews.RemoteView; -import com.android.internal.R; - - /** * FrameLayout is designed to block out an area on the screen to display * a single item. Generally, FrameLayout should be used to hold a single child view, because it can @@ -171,6 +164,10 @@ public class FrameLayout extends ViewGroup { mPaddingBottom + mForegroundPaddingBottom; } + @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class); + } /** * {@inheritDoc} diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index ad939be13a44..ba868a15ef1e 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -16,6 +16,7 @@ package android.widget; +import android.view.ViewParent; import com.android.internal.R; import android.annotation.IntDef; @@ -37,6 +38,8 @@ import android.widget.RemoteViews.RemoteView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; /** * A Layout that arranges its children in a single column or a single row. The direction of @@ -644,6 +647,60 @@ public class LinearLayout extends ViewGroup { } } + @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + // This implementation is almost exactly equivalent to the default implementation + // offered to the rest of the framework in ViewGroup, but we treat weight to be + // functionally equivalent to MATCH_PARENT along the orientation axis. + + if (!checkPartialLayoutParams(child, LayoutParams.class)) return axisFilter; + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (child.didLayoutParamsChange()) { + // Anything could have changed about our previous assumptions. + return axisFilter; + } + + // Our layout can always end up depending on a WRAP_CONTENT child. + final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0) + | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter; + + if (wrapAxisFilter == axisFilter) { + // We know all queried axes are affected, just return early. + return wrapAxisFilter; + } + + // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine + // that our layout will remain stable within our parent. We need to ask. + int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0) + | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter; + + // For LinearLayout, a nonzero weight is equivalent to MATCH_PARENT for this purpose. + if (lp.weight > 0) { + if (mOrientation == HORIZONTAL) { + matchAxisFilter |= FLAG_LAYOUT_AXIS_HORIZONTAL & axisFilter; + } else { + matchAxisFilter |= FLAG_LAYOUT_AXIS_VERTICAL & axisFilter; + } + } + + if (matchAxisFilter != 0) { + final ViewParent parent = getParent(); + if (parent != null) { + // If our parent depends on us for an axis, then our layout can also be affected + // by a MATCH_PARENT child along that axis. + return getParent().findDependentLayoutAxes(this, matchAxisFilter) + | wrapAxisFilter; + } + + // If we don't have a parent, assume we're affected + // in any determined affected direction. + return matchAxisFilter | wrapAxisFilter; + } + + // Two exact sizes and LayoutParams didn't change. We're safe. + return 0; + } + /** * Determines where to position dividers between children. * diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 53ca6d1ba3b1..b43ea76976e1 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1158,7 +1158,7 @@ public class ListView extends AbsListView { final View child = obtainView(0, mIsScrap); // Lay out child directly against the parent measure spec so that - // we can obtain exected minimum width and height. + // we can obtain expected minimum width and height. measureScrapChild(child, 0, widthMeasureSpec, heightSize); childWidth = child.getMeasuredWidth(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3e6d121ba4c8..eaf4fe2c71b2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6801,10 +6801,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (!compressText(ellipsisWidth)) { - final int height = mLayoutParams.height; // If the size of the view does not depend on the size of the text, try to // start the marquee immediately - if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { + final ViewParent parent = getParent(); + if (parent != null && parent.findDependentLayoutAxes(this, + ViewParent.FLAG_LAYOUT_AXIS_VERTICAL) == 0) { startMarquee(); } else { // Defer the start of the marquee until we know our width (see setFrame()) @@ -7200,37 +7201,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * new view layout. */ private void checkForResize() { - boolean sizeChanged = false; - - if (mLayout != null) { - // Check if our width changed - if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { - sizeChanged = true; - invalidate(); - } - - // Check if our height changed - if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { - int desiredHeight = getDesiredHeight(); - - if (desiredHeight != this.getHeight()) { - sizeChanged = true; - } - } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { - if (mDesiredHeightAtMeasure >= 0) { - int desiredHeight = getDesiredHeight(); - - if (desiredHeight != mDesiredHeightAtMeasure) { - sizeChanged = true; - } - } - } - } - - if (sizeChanged) { - requestLayout(); - // caller will have already invalidated - } + // Always request a layout. The parent will perform the correct version + // of the intended optimizations as part of requestLayoutForChild. + requestLayout(); } /** @@ -7238,56 +7211,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * or merely a new text layout. */ private void checkForRelayout() { - // If we have a fixed width, we can just swap in a new text layout - // if the text height stays the same or if the view height is fixed. - - if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || - (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && - (mHint == null || mHintLayout != null) && - (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { - // Static width, so try making a new text layout. - - int oldht = mLayout.getHeight(); - int want = mLayout.getWidth(); - int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); - - /* - * No need to bring the text into view, since the size is not - * changing (unless we do the requestLayout(), in which case it - * will happen at measure). - */ - makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, - mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), - false); - - if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { - // In a fixed-height view, so use our new text layout. - if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && - mLayoutParams.height != LayoutParams.MATCH_PARENT) { - invalidate(); - return; - } - - // Dynamic height, but height has stayed the same, - // so use our new text layout. - if (mLayout.getHeight() == oldht && - (mHintLayout == null || mHintLayout.getHeight() == oldht)) { - invalidate(); - return; - } - } - - // We lose: the height has changed and we have a dynamic height. - // Request a new view layout using our new text layout. - requestLayout(); - invalidate(); - } else { - // Dynamic width, so we have no choice but to request a new - // view layout with a new text layout. - nullLayouts(); - requestLayout(); - invalidate(); - } + // Always request a layout. The parent will perform the correct version + // of the intended optimizations as part of requestLayoutForChild. + nullLayouts(); + requestLayout(); } @Override diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java index acbf5eb8d699..6e565138eff9 100644 --- a/core/java/android/widget/Toolbar.java +++ b/core/java/android/widget/Toolbar.java @@ -1368,6 +1368,11 @@ public class Toolbar extends ViewGroup { } @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 0; int height = 0; diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.aidl b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl new file mode 100644 index 000000000000..529527bd394b --- /dev/null +++ b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl @@ -0,0 +1,19 @@ +/* +** Copyright 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.internal.app; + +parcelable EphemeralResolveInfo; diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.java b/core/java/com/android/internal/app/EphemeralResolveInfo.java new file mode 100644 index 000000000000..0e7ef05dd150 --- /dev/null +++ b/core/java/com/android/internal/app/EphemeralResolveInfo.java @@ -0,0 +1,113 @@ +/* + * 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.internal.app; + +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +/** + * Information that is returned when resolving ephemeral + * applications. + */ +public final class EphemeralResolveInfo implements Parcelable { + public static final String SHA_ALGORITHM = "SHA-256"; + private byte[] mDigestBytes; + private int mDigestPrefix; + private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>(); + + public EphemeralResolveInfo(Uri uri, List<IntentFilter> filters) { + generateDigest(uri); + mFilters.addAll(filters); + } + + private EphemeralResolveInfo(Parcel in) { + readFromParcel(in); + } + + public byte[] getDigestBytes() { + return mDigestBytes; + } + + public int getDigestPrefix() { + return mDigestPrefix; + } + + public List<IntentFilter> getFilters() { + return mFilters; + } + + private void generateDigest(Uri uri) { + try { + final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM); + final byte[] hostBytes = uri.getHost().getBytes(); + final byte[] digestBytes = digest.digest(hostBytes); + mDigestBytes = digestBytes; + mDigestPrefix = + digestBytes[0] << 24 + | digestBytes[1] << 16 + | digestBytes[2] << 8 + | digestBytes[3] << 0; + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("could not find digest algorithm"); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + if (mDigestBytes == null) { + out.writeInt(0); + } else { + out.writeInt(mDigestBytes.length); + out.writeByteArray(mDigestBytes); + } + out.writeInt(mDigestPrefix); + out.writeList(mFilters); + } + + private void readFromParcel(Parcel in) { + int digestBytesSize = in.readInt(); + if (digestBytesSize > 0) { + mDigestBytes = new byte[digestBytesSize]; + in.readByteArray(mDigestBytes); + } + mDigestPrefix = in.readInt(); + in.readList(mFilters, null /*loader*/); + } + + public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR + = new Parcelable.Creator<EphemeralResolveInfo>() { + public EphemeralResolveInfo createFromParcel(Parcel in) { + return new EphemeralResolveInfo(in); + } + + public EphemeralResolveInfo[] newArray(int size) { + return new EphemeralResolveInfo[size]; + } + }; +} diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java new file mode 100644 index 000000000000..65530f270128 --- /dev/null +++ b/core/java/com/android/internal/app/EphemeralResolverService.java @@ -0,0 +1,98 @@ +/* + * 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.internal.app; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; + +import java.util.List; + +/** + * Base class for implementing the resolver service. + * @hide + */ +public abstract class EphemeralResolverService extends Service { + public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO"; + public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE"; + private Handler mHandler; + + /** + * Called to retrieve resolve info for ephemeral applications. + * + * @param digestPrefix The hash prefix of the ephemeral's domain. + */ + protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix); + + @Override + protected final void attachBaseContext(Context base) { + super.attachBaseContext(base); + mHandler = new ServiceHandler(base.getMainLooper()); + } + + @Override + public final IBinder onBind(Intent intent) { + return new IEphemeralResolver.Stub() { + @Override + public void getEphemeralResolveInfoList( + IRemoteCallback callback, int digestPrefix, int sequence) { + mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO, + digestPrefix, sequence, callback) + .sendToTarget(); + } + }; + } + + private final class ServiceHandler extends Handler { + public static final int MSG_GET_EPHEMERAL_RESOLVE_INFO = 1; + + public ServiceHandler(Looper looper) { + super(looper, null /*callback*/, true /*async*/); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + final int action = message.what; + switch (action) { + case MSG_GET_EPHEMERAL_RESOLVE_INFO: { + final IRemoteCallback callback = (IRemoteCallback) message.obj; + final List<EphemeralResolveInfo> resolveInfo = + getEphemeralResolveInfoList(message.arg1); + final Bundle data = new Bundle(); + data.putInt(EXTRA_SEQUENCE, message.arg2); + data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo); + try { + callback.sendResult(data); + } catch (RemoteException e) { + } + } break; + + default: { + throw new IllegalArgumentException("Unknown message: " + action); + } + } + } + } +} diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl new file mode 100644 index 000000000000..40429ee2cccb --- /dev/null +++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl @@ -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. + */ + +package com.android.internal.app; + +import android.content.Intent; +import android.os.IRemoteCallback; + +oneway interface IEphemeralResolver { + void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence); +} diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 077cebc4ebf9..27fe03cec97b 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -25,8 +25,8 @@ import com.android.internal.view.menu.MenuDialogHelper; import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; +import com.android.internal.widget.DecorCaptionView; import com.android.internal.widget.FloatingToolbar; -import com.android.internal.widget.NonClientDecorView; import android.animation.Animator; import android.animation.ObjectAnimator; @@ -88,6 +88,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private static final boolean SWEEP_OPEN_MENU = false; + // The height of a window which has focus in DIP. + private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; + // The height of a window which has not in DIP. + private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5; + + // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer + // size calculation takes the shadow size into account. We set the elevation currently + // to max until the first layout command has been executed. + private boolean mAllowUpdateElevation = false; + + private boolean mElevationAdjustedForStack = false; + int mDefaultOpacity = PixelFormat.OPAQUE; /** The feature ID of the panel, or -1 if this is the application's DecorView */ @@ -101,8 +113,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private final Rect mFrameOffsets = new Rect(); - // True if a non client area decor exists. - private boolean mHasNonClientDecor = false; + private boolean mHasCaption = false; private boolean mChanging; @@ -161,18 +172,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Rect mTempRect; private Rect mOutsets = new Rect(); - // This is the non client decor view for the window, containing the caption and window control + // This is the caption view for the window, containing the caption and window control // buttons. The visibility of this decor depends on the workspace and the window type. // If the window type does not require such a view, this member might be null. - NonClientDecorView mNonClientDecorView; + DecorCaptionView mDecorCaptionView; - // The non client decor needs to adapt to the used workspace. Since querying and changing the - // workspace is expensive, this is the workspace value the window is currently set up for. - int mWorkspaceId; + // Stack window is currently in. Since querying and changing the stack is expensive, + // this is the stack value the window is currently set up for. + int mStackId; private boolean mWindowResizeCallbacksAdded = false; - public BackdropFrameRenderer mBackdropFrameRenderer = null; + BackdropFrameRenderer mBackdropFrameRenderer = null; private Drawable mResizingBackgroundDrawable; private Drawable mCaptionBackgroundDrawable; @@ -191,7 +202,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind setWindow(window); } - public void setBackgroundFallback(int resId) { + void setBackgroundFallback(int resId) { mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null); setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback()); } @@ -351,10 +362,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @Override public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); - if (mHasNonClientDecor && mNonClientDecorView.mVisible) { - // Don't dispatch ACTION_DOWN to the non client decor if the window is - // resizable and the event was (starting) outside the window. - // Window resizing events should be handled by WindowManager. + if (mHasCaption && isShowingCaption()) { + // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event + // was (starting) outside the window. Window resizing events should be handled by + // WindowManager. // TODO: Investigate how to handle the outside touch in window manager // without generating these events. // Currently we receive these because we need to enlarge the window's @@ -630,6 +641,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (mOutsets.top > 0) { offsetTopAndBottom(-mOutsets.top); } + + // If the application changed its SystemUI metrics, we might also have to adapt + // our shadow elevation. + updateElevation(); + mAllowUpdateElevation = true; } @Override @@ -781,11 +797,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } - public void startChanging() { + void startChanging() { mChanging = true; } - public void finishChanging() { + void finishChanging() { mChanging = false; drawableChanged(); } @@ -1138,7 +1154,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind invalidate(); int opacity = PixelFormat.OPAQUE; - if (windowHasShadow()) { + if (ActivityManager.StackId.hasWindowShadow(mStackId)) { // If the window has a shadow, it must be translucent. opacity = PixelFormat.TRANSLUCENT; } else{ @@ -1213,6 +1229,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (mFloatingActionMode != null) { mFloatingActionMode.onWindowFocusChanged(hasWindowFocus); } + + updateElevation(); } @Override @@ -1495,38 +1513,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } /** - * Informs the decor if a non client decor is attached and visible. + * Informs the decor if the caption is attached and visible. * @param attachedAndVisible true when the decor is visible. - * Note that this will even be called if there is no non client decor. + * Note that this will even be called if there is no caption. **/ - void enableNonClientDecor(boolean attachedAndVisible) { - if (mHasNonClientDecor != attachedAndVisible) { - mHasNonClientDecor = attachedAndVisible; + void enableCaption(boolean attachedAndVisible) { + if (mHasCaption != attachedAndVisible) { + mHasCaption = attachedAndVisible; if (getForeground() != null) { drawableChanged(); } } } - /** - * Returns true if the window has a non client decor. - * @return If there is a non client decor - even if it is not visible. - **/ - private boolean windowHasNonClientDecor() { - return mHasNonClientDecor; - } - - /** - * Returns true if the Window is free floating and has a shadow (although at some times - * it might not be displaying it, e.g. during a resize). Note that non overlapping windows - * do not have a shadow since it could not be seen anyways (a small screen / tablet - * "tiles" the windows side by side but does not overlap them). - * @return Returns true when the window has a shadow created by the non client decor. - **/ - private boolean windowHasShadow() { - return windowHasNonClientDecor() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId); - } - void setWindow(PhoneWindow phoneWindow) { mWindow = phoneWindow; Context context = getContext(); @@ -1537,91 +1536,89 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } void onConfigurationChanged() { - if (mNonClientDecorView != null) { - int workspaceId = getWorkspaceId(); - if (mWorkspaceId != workspaceId) { - mWorkspaceId = workspaceId; + int workspaceId = getStackId(); + if (mDecorCaptionView != null) { + if (mStackId != workspaceId) { + mStackId = workspaceId; // We might have to change the kind of surface before we do anything else. - mNonClientDecorView.onConfigurationChanged( - ActivityManager.StackId.hasWindowDecor(mWorkspaceId), - ActivityManager.StackId.hasWindowShadow(mWorkspaceId)); - enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId)); + mDecorCaptionView.onConfigurationChanged( + ActivityManager.StackId.hasWindowDecor(mStackId)); + enableCaption(ActivityManager.StackId.hasWindowDecor(workspaceId)); } } + initializeElevation(); } View onResourcesLoaded(LayoutInflater inflater, int layoutResource) { - mWorkspaceId = getWorkspaceId(); + mStackId = getStackId(); mResizingBackgroundDrawable = getResizingBackgroundDrawable( mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource); mCaptionBackgroundDrawable = - getContext().getDrawable(R.drawable.non_client_decor_title_focused); + getContext().getDrawable(R.drawable.decor_caption_title_focused); if (mBackdropFrameRenderer != null) { mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable); } - mNonClientDecorView = createNonClientDecorView(inflater); + mDecorCaptionView = createDecorCaptionView(inflater); final View root = inflater.inflate(layoutResource, null); - if (mNonClientDecorView != null) { - if (mNonClientDecorView.getParent() == null) { - addView(mNonClientDecorView, + if (mDecorCaptionView != null) { + if (mDecorCaptionView.getParent() == null) { + addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } - mNonClientDecorView.addView(root, - new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mDecorCaptionView.addView(root, + new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; + initializeElevation(); return root; } - // Free floating overlapping windows require a non client decor with a caption and shadow.. - private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) { - NonClientDecorView nonClientDecorView = null; - for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) { + // Free floating overlapping windows require a caption. + private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) { + DecorCaptionView DecorCaptionView = null; + for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) { View view = getChildAt(i); - if (view instanceof NonClientDecorView) { + if (view instanceof DecorCaptionView) { // The decor was most likely saved from a relaunch - so reuse it. - nonClientDecorView = (NonClientDecorView) view; + DecorCaptionView = (DecorCaptionView) view; removeViewAt(i); } } final WindowManager.LayoutParams attrs = mWindow.getAttributes(); - boolean isApplication = attrs.type == TYPE_BASE_APPLICATION || + final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION || attrs.type == TYPE_APPLICATION; - // Only a non floating application window on one of the allowed workspaces can get a non - // client decor. - final boolean hasNonClientDecor = ActivityManager.StackId.hasWindowDecor(mWorkspaceId); - if (!mWindow.isFloating() && isApplication && hasNonClientDecor) { + // Only a non floating application window on one of the allowed workspaces can get a caption + if (!mWindow.isFloating() && isApplication + && ActivityManager.StackId.hasWindowDecor(mStackId)) { // Dependent on the brightness of the used title we either use the // dark or the light button frame. - if (nonClientDecorView == null) { + if (DecorCaptionView == null) { Context context = getContext(); TypedValue value = new TypedValue(); context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true); inflater = inflater.from(context); if (Color.luminance(value.data) < 0.5) { - nonClientDecorView = (NonClientDecorView) inflater.inflate( - R.layout.non_client_decor_dark, null); + DecorCaptionView = (DecorCaptionView) inflater.inflate( + R.layout.decor_caption_dark, null); } else { - nonClientDecorView = (NonClientDecorView) inflater.inflate( - R.layout.non_client_decor_light, null); + DecorCaptionView = (DecorCaptionView) inflater.inflate( + R.layout.decor_caption_light, null); } } - nonClientDecorView.setPhoneWindow(mWindow, - ActivityManager.StackId.hasWindowDecor(mWorkspaceId), - ActivityManager.StackId.hasWindowShadow(mWorkspaceId)); + DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/); } else { - nonClientDecorView = null; + DecorCaptionView = null; } - // Tell the decor if it has a visible non client decor. - enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor); - return nonClientDecorView; + // Tell the decor if it has a visible caption. + enableCaption(DecorCaptionView != null); + return DecorCaptionView; } /** @@ -1652,12 +1649,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } /** - * Returns the Id of the workspace which contains this window. - * Note that if no workspace can be determined - which usually means that it was not - * created for an activity - the fullscreen workspace ID will be returned. - * @return Returns the workspace stack id which contains this window. + * Returns the Id of the stack which contains this window. + * Note that if no stack can be determined - which usually means that it was not + * created for an activity - the fullscreen stack ID will be returned. + * @return Returns the stack id which contains this window. **/ - private int getWorkspaceId() { + private int getStackId() { int workspaceId = INVALID_STACK_ID; final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback(); if (callback != null) { @@ -1674,12 +1671,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } void clearContentView() { - if (mNonClientDecorView != null) { - if (mNonClientDecorView.getChildCount() > 1) { - mNonClientDecorView.removeViewAt(1); - } + if (mDecorCaptionView != null) { + mDecorCaptionView.removeContentView(); } else { - // This window doesn't have non client decor, so we need to just remove the + // This window doesn't have caption, so we need to just remove the // children of the decor view. removeAllViews(); } @@ -1749,18 +1744,60 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } + /** + * The elevation gets set for the first time and the framework needs to be informed that + * the surface layer gets created with the shadow size in mind. + */ + private void initializeElevation() { + // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed. + mAllowUpdateElevation = false; + updateElevation(); + } + private void updateElevation() { - if (mNonClientDecorView != null) { - mNonClientDecorView.updateElevation(); + float elevation = 0; + final boolean wasAdjustedForStack = mElevationAdjustedForStack; + // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null) + // since the shadow is bound to the content size and not the target size. + if (ActivityManager.StackId.hasWindowShadow(mStackId) + && mBackdropFrameRenderer == null) { + elevation = hasWindowFocus() ? + DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; + // TODO(skuhne): Remove this if clause once b/22668382 got fixed. + if (!mAllowUpdateElevation) { + elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; + } + // Convert the DP elevation into physical pixels. + elevation = dipToPx(elevation); + mElevationAdjustedForStack = true; + } else { + mElevationAdjustedForStack = false; + } + + // Don't change the elevation if we didn't previously adjust it for the stack it was in + // or it didn't change. + if ((wasAdjustedForStack || mElevationAdjustedForStack) + && getElevation() != elevation) { + mWindow.setElevation(elevation); } } boolean isShowingCaption() { - return mNonClientDecorView != null && mNonClientDecorView.isShowingDecor(); + return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing(); } int getCaptionHeight() { - return isShowingCaption() ? mNonClientDecorView.getDecorCaptionHeight() : 0; + return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0; + } + + /** + * Converts a DIP measure into physical pixels. + * @param dip The dip value. + * @return Returns the number of pixels. + */ + private float dipToPx(float dip) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, + getResources().getDisplayMetrics()); } private static class ColorViewState { diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index c3a7460e5752..3e65320d3372 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -360,6 +360,11 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar } @Override + public int findDependentLayoutAxes(View child, int axisFilter) { + return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { pullChildren(); diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/DecorCaptionView.java index 33b8e0515ab0..16e829616704 100644 --- a/core/java/com/android/internal/widget/NonClientDecorView.java +++ b/core/java/com/android/internal/widget/DecorCaptionView.java @@ -23,52 +23,37 @@ import android.os.RemoteException; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; -import android.widget.LinearLayout; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.Window; import android.util.Log; -import android.util.TypedValue; import com.android.internal.R; -import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneWindow; /** - * This class represents the special screen elements to control a window on free form - * environment. All thse screen elements are added in the "non client area" which is the area of - * the window which is handled by the OS and not the application. + * This class represents the special screen elements to control a window on freeform + * environment. * As such this class handles the following things: * <ul> * <li>The caption, containing the system buttons like maximize, close and such as well as * allowing the user to drag the window around.</li> - * <li>The shadow - which is changing dependent on the window focus.</li> - * <li>The border around the client area (if there is one).</li> - * <li>The resize handles which allow to resize the window.</li> - * </ul> * After creating the view, the function * {@link #setPhoneWindow} needs to be called to make * the connection to it's owning PhoneWindow. * Note: At this time the application can change various attributes of the DecorView which * will break things (in settle/unexpected ways): * <ul> - * <li>setElevation</li> * <li>setOutlineProvider</li> * <li>setSurfaceFormat</li> * <li>..</li> * </ul> - * This will be mitigated once b/22527834 will be addressed. */ -public class NonClientDecorView extends LinearLayout +public class DecorCaptionView extends ViewGroup implements View.OnClickListener, View.OnTouchListener { - private final static String TAG = "NonClientDecorView"; - // The height of a window which has focus in DIP. - private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; - // The height of a window which has not in DIP. - private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5; + private final static String TAG = "DecorCaptionView"; private PhoneWindow mOwner = null; - private boolean mWindowHasShadow = false; - private boolean mShowDecor = false; + private boolean mShow = false; // True if the window is being dragged. private boolean mDragging = false; @@ -76,37 +61,34 @@ public class NonClientDecorView extends LinearLayout // True when the left mouse button got released while dragging. private boolean mLeftMouseButtonReleased; - // True if this window is resizable (which is currently only true when the decor is shown). - public boolean mVisible = false; - - // The current focus state of the window for updating the window elevation. - private boolean mWindowHasFocus = true; + private boolean mOverlayWithAppContent = false; - // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer - // size calculation takes the shadow size into account. We set the elevation currently - // to max until the first layout command has been executed. - private boolean mAllowUpdateElevation = false; + private View mCaption; + private View mContent; - public NonClientDecorView(Context context) { + public DecorCaptionView(Context context) { super(context); } - public NonClientDecorView(Context context, AttributeSet attrs) { + public DecorCaptionView(Context context, AttributeSet attrs) { super(context, attrs); } - public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) { + public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } - public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) { + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mCaption = getChildAt(0); + } + + public void setPhoneWindow(PhoneWindow owner, boolean show) { mOwner = owner; - mWindowHasShadow = windowHasShadow; - mShowDecor = showDecor; + mShow = show; + mOverlayWithAppContent = owner.getOverlayDecorCaption(); updateCaptionVisibility(); - if (mWindowHasShadow) { - initializeElevation(); - } // By changing the outline provider to BOUNDS, the window can remove its // background without removing the shadow. mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS); @@ -122,8 +104,8 @@ public class NonClientDecorView extends LinearLayout // input device we are listening to. switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: - if (!mShowDecor) { - // When there is no decor we should not react to anything. + if (!mShow) { + // When there is no caption we should not react to anything. return false; } // A drag action is started if we aren't dragging already and the starting event is @@ -162,17 +144,12 @@ public class NonClientDecorView extends LinearLayout } /** - * The phone window configuration has changed and the decor needs to be updated. - * @param showDecor True if the decor should be shown. - * @param windowHasShadow True when the window should show a shadow. - **/ - public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) { - mShowDecor = showDecor; + * The phone window configuration has changed and the caption needs to be updated. + * @param show True if the caption should be shown. + */ + public void onConfigurationChanged(boolean show) { + mShow = show; updateCaptionVisibility(); - if (windowHasShadow != mWindowHasShadow) { - mWindowHasShadow = windowHasShadow; - initializeElevation(); - } } @Override @@ -185,31 +162,62 @@ public class NonClientDecorView extends LinearLayout } @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - mWindowHasFocus = hasWindowFocus; - updateElevation(); - super.onWindowFocusChanged(hasWindowFocus); + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (!(params instanceof MarginLayoutParams)) { + throw new IllegalArgumentException( + "params " + params + " must subclass MarginLayoutParams"); + } + // Make sure that we never get more then one client area in our view. + if (index >= 2 || getChildCount() >= 2) { + throw new IllegalStateException("DecorCaptionView can only handle 1 client view"); + } + // To support the overlaying content in the caption, we need to put the content view as the + // first child to get the right Z-Ordering. + super.addView(child, 0, params); + mContent = child; } @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - // If the application changed its SystemUI metrics, we might also have to adapt - // our shadow elevation. - updateElevation(); - mAllowUpdateElevation = true; + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int captionHeight; + if (mCaption.getVisibility() != View.GONE) { + measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0); + captionHeight = mCaption.getMeasuredHeight(); + } else { + captionHeight = 0; + } + if (mContent != null) { + if (mOverlayWithAppContent) { + measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); + } else { + measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, + captionHeight); + } + } - super.onLayout(changed, left, top, right, bottom); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); } @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - // Make sure that we never get more then one client area in our view. - if (index >= 2 || getChildCount() >= 2) { - throw new IllegalStateException("NonClientDecorView can only handle 1 client view"); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int captionHeight; + if (mCaption.getVisibility() != View.GONE) { + mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight()); + captionHeight = mCaption.getBottom() - mCaption.getTop(); + } else { + captionHeight = 0; } - super.addView(child, index, params); - } + if (mContent != null) { + if (mOverlayWithAppContent) { + mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight()); + } else { + mContent.layout(0, captionHeight, mContent.getMeasuredWidth(), + captionHeight + mContent.getMeasuredHeight()); + } + } + } /** * Determine if the workspace is entirely covered by the window. * @return Returns true when the window is filling the entire screen/workspace. @@ -224,65 +232,10 @@ public class NonClientDecorView extends LinearLayout * Updates the visibility of the caption. **/ private void updateCaptionVisibility() { - // Don't show the decor if the window has e.g. entered full screen. - boolean invisible = isFillingScreen() || !mShowDecor; - View caption = getChildAt(0); - caption.setVisibility(invisible ? GONE : VISIBLE); - caption.setOnTouchListener(this); - mVisible = !invisible; - } - - /** - * The elevation gets set for the first time and the framework needs to be informed that - * the surface layer gets created with the shadow size in mind. - **/ - private void initializeElevation() { - // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed. - mAllowUpdateElevation = false; - if (mWindowHasShadow) { - updateElevation(); - } else { - mOwner.setElevation(0); - } - } - - /** - * The shadow height gets controlled by the focus to visualize highlighted windows. - * Note: This will overwrite application elevation properties. - * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI - * will get no shadow as they are expected to be "full screen". - **/ - public void updateElevation() { - float elevation = 0; - // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow - // is bound to the content size and not the target size. - if (mWindowHasShadow - && ((DecorView) mOwner.getDecorView()).mBackdropFrameRenderer == null) { - boolean fill = isFillingScreen(); - elevation = fill ? 0 : - (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : - DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP); - // TODO(skuhne): Remove this if clause once b/22668382 got fixed. - if (!mAllowUpdateElevation && !fill) { - elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; - } - // Convert the DP elevation into physical pixels. - elevation = dipToPx(elevation); - } - // Don't change the elevation if it didn't change since it can require some time. - if (mOwner.getDecorView().getElevation() != elevation) { - mOwner.setElevation(elevation); - } - } - - /** - * Converts a DIP measure into physical pixels. - * @param dip The dip value. - * @return Returns the number of pixels. - */ - private float dipToPx(float dip) { - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, - getResources().getDisplayMetrics()); + // Don't show the caption if the window has e.g. entered full screen. + boolean invisible = isFillingScreen() || !mShow; + mCaption.setVisibility(invisible ? GONE : VISIBLE); + mCaption.setOnTouchListener(this); } /** @@ -299,12 +252,43 @@ public class NonClientDecorView extends LinearLayout } } - public boolean isShowingDecor() { - return mShowDecor; + public boolean isCaptionShowing() { + return mShow; + } + + public int getCaptionHeight() { + return (mCaption != null) ? mCaption.getHeight() : 0; } - public int getDecorCaptionHeight() { - final View caption = getChildAt(0); - return (caption != null) ? caption.getHeight() : 0; + public void removeContentView() { + if (mContent != null) { + removeView(mContent); + mContent = null; + } + } + + public View getCaption() { + return mCaption; + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT, + MarginLayoutParams.MATCH_PARENT); + } + + @Override + protected LayoutParams generateLayoutParams(LayoutParams p) { + return new MarginLayoutParams(p); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof MarginLayoutParams; } } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 65e8058f25ba..bd41c5d37336 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -580,7 +580,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) char heapminfreeOptsBuf[sizeof("-XX:HeapMinFree=")-1 + PROPERTY_VALUE_MAX]; char heapmaxfreeOptsBuf[sizeof("-XX:HeapMaxFree=")-1 + PROPERTY_VALUE_MAX]; char usejitOptsBuf[sizeof("-Xusejit:")-1 + PROPERTY_VALUE_MAX]; - char jitcodecachesizeOptsBuf[sizeof("-Xjitcodecachesize:")-1 + PROPERTY_VALUE_MAX]; + char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX]; + char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX]; char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX]; char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX]; char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX]; @@ -684,7 +685,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) * JIT related options. */ parseRuntimeOption("dalvik.vm.usejit", usejitOptsBuf, "-Xusejit:"); - parseRuntimeOption("dalvik.vm.jitcodecachesize", jitcodecachesizeOptsBuf, "-Xjitcodecachesize:"); + parseRuntimeOption("dalvik.vm.jitmaxsize", jitmaxsizeOptsBuf, "-Xjitmaxsize:"); + parseRuntimeOption("dalvik.vm.jitinitialsize", jitinitialsizeOptsBuf, "-Xjitinitialsize:"); parseRuntimeOption("dalvik.vm.jitthreshold", jitthresholdOptsBuf, "-Xjitthreshold:"); property_get("ro.config.low_ram", propBuf, ""); diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp index 245aa0f9020e..0927120f1cf5 100644 --- a/core/jni/android_util_PathParser.cpp +++ b/core/jni/android_util_PathParser.cpp @@ -18,19 +18,22 @@ #include <PathParser.h> #include <SkPath.h> +#include <utils/VectorDrawableUtils.h> #include <android/log.h> #include "core_jni_helpers.h" namespace android { +using namespace uirenderer; + static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr, jint strLength) { const char* pathString = env->GetStringUTFChars(inputPathStr, NULL); SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle); - android::uirenderer::PathParser::ParseResult result; - android::uirenderer::PathParser::parseStringForSkPath(skPath, &result, pathString, strLength); + PathParser::ParseResult result; + PathParser::parseStringForSkPath(skPath, &result, pathString, strLength); env->ReleaseStringUTFChars(inputPathStr, pathString); if (result.failureOccurred) { ALOGE(result.failureMessage.c_str()); @@ -38,8 +41,74 @@ static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring return !result.failureOccurred; } +static long createEmptyPathData(JNIEnv*, jobject) { + PathData* pathData = new PathData(); + return reinterpret_cast<jlong>(pathData); +} + +static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + PathData* newPathData = new PathData(*pathData); + return reinterpret_cast<jlong>(newPathData); +} + +static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) { + const char* pathString = env->GetStringUTFChars(inputStr, NULL); + PathData* pathData = new PathData(); + PathParser::ParseResult result; + PathParser::getPathDataFromString(pathData, &result, pathString, strLength); + env->ReleaseStringUTFChars(inputStr, pathString); + if (!result.failureOccurred) { + return reinterpret_cast<jlong>(pathData); + } else { + delete pathData; + ALOGE(result.failureMessage.c_str()); + return NULL; + } +} + +static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr, + jlong toPathDataPtr, jfloat fraction) { + PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr); + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr); + return VectorDrawableUtils::interpolatePathData(outPathData, *fromPathData, + *toPathData, fraction); +} + +static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataHandle); + delete pathData; +} + +static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) { + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr); + return VectorDrawableUtils::canMorph(*fromPathData, *toPathData); +} + +static void setPathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr) { + PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr); + PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr); + *outPathData = *fromPathData; +} + +static void setSkPathFromPathData(JNIEnv*, jobject, jlong outPathPtr, jlong pathDataPtr) { + PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr); + SkPath* skPath = reinterpret_cast<SkPath*>(outPathPtr); + VectorDrawableUtils::verbsToPath(skPath, *pathData); +} + static const JNINativeMethod gMethods[] = { - {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath} + {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath}, + {"nCreateEmptyPathData", "!()J", (void*)createEmptyPathData}, + {"nCreatePathData", "!(J)J", (void*)createPathData}, + {"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath}, + {"nInterpolatePathData", "!(JJJF)Z", (void*)interpolatePathData}, + {"nFinalize", "!(J)V", (void*)deletePathData}, + {"nCanMorph", "!(JJ)Z", (void*)canMorphPathData}, + {"nSetPathData", "!(JJ)V", (void*)setPathData}, + {"nCreatePathFromPathData", "!(JJ)V", (void*)setSkPathFromPathData}, }; int register_android_util_PathParser(JNIEnv* env) { diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 3f1be456c199..73c74876c292 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -21,6 +21,7 @@ #include <linux/fs.h> #include <list> +#include <sstream> #include <string> #include <fcntl.h> @@ -74,8 +75,10 @@ enum MountExternalKind { MOUNT_EXTERNAL_WRITE = 3, }; -static void RuntimeAbort(JNIEnv* env) { - env->FatalError("RuntimeAbort"); +static void RuntimeAbort(JNIEnv* env, int line, const char* msg) { + std::ostringstream oss; + oss << __FILE__ << ":" << line << ": " << msg; + env->FatalError(oss.str().c_str()); } // This signal handler is for zygote mode, since the zygote must reap its children @@ -169,12 +172,11 @@ static void SetGids(JNIEnv* env, jintArray javaGids) { ScopedIntArrayRO gids(env, javaGids); if (gids.get() == NULL) { - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "Getting gids int array failed"); } int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0])); if (rc == -1) { - ALOGE("setgroups failed"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "setgroups failed"); } } @@ -194,8 +196,7 @@ static void SetRLimits(JNIEnv* env, jobjectArray javaRlimits) { ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i)); ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get())); if (javaRlimit.size() != 3) { - ALOGE("rlimits array must have a second dimension of size 3"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "rlimits array must have a second dimension of size 3"); } rlim.rlim_cur = javaRlimit[1]; @@ -205,7 +206,7 @@ static void SetRLimits(JNIEnv* env, jobjectArray javaRlimits) { if (rc == -1) { ALOGE("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur, rlim.rlim_max); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "setrlimit failed"); } } } @@ -216,8 +217,7 @@ extern "C" int gMallocLeakZygoteChild; static void EnableKeepCapabilities(JNIEnv* env) { int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); if (rc == -1) { - ALOGE("prctl(PR_SET_KEEPCAPS) failed"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "prctl(PR_SET_KEEPCAPS) failed"); } } @@ -229,8 +229,7 @@ static void DropCapabilitiesBoundingSet(JNIEnv* env) { ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify " "your kernel is compiled with file capabilities support"); } else { - ALOGE("prctl(PR_CAPBSET_DROP) failed"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "prctl(PR_CAPBSET_DROP) failed"); } } } @@ -251,7 +250,7 @@ static void SetCapabilities(JNIEnv* env, int64_t permitted, int64_t effective) { if (capset(&capheader, &capdata[0]) == -1) { ALOGE("capset(%" PRId64 ", %" PRId64 ") failed", permitted, effective); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "capset failed"); } } @@ -259,7 +258,7 @@ static void SetSchedulerPolicy(JNIEnv* env) { errno = -set_sched_policy(0, SP_DEFAULT); if (errno != 0) { ALOGE("set_sched_policy(0, SP_DEFAULT) failed"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "set_sched_policy(0, SP_DEFAULT) failed"); } } @@ -370,8 +369,7 @@ static void DetachDescriptors(JNIEnv* env, jintArray fdsToClose) { jsize count = env->GetArrayLength(fdsToClose); ScopedIntArrayRO ar(env, fdsToClose); if (ar.get() == NULL) { - ALOGE("Bad fd array"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "Bad fd array"); } jsize i; int devnull; @@ -379,13 +377,13 @@ static void DetachDescriptors(JNIEnv* env, jintArray fdsToClose) { devnull = open("/dev/null", O_RDWR); if (devnull < 0) { ALOGE("Failed to open /dev/null: %s", strerror(errno)); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "Failed to open /dev/null"); continue; } ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno)); if (dup2(devnull, ar[i]) < 0) { ALOGE("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno)); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "Failed dup2()"); } close(devnull); } @@ -493,8 +491,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra // FUSE hasn't been created yet by init. // In either case, continue without external storage. } else { - ALOGE("Cannot continue without emulated storage"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage"); } } @@ -522,13 +519,13 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra int rc = setresgid(gid, gid, gid); if (rc == -1) { ALOGE("setresgid(%d) failed: %s", gid, strerror(errno)); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "setresgid failed"); } rc = setresuid(uid, uid, uid); if (rc == -1) { ALOGE("setresuid(%d) failed: %s", uid, strerror(errno)); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "setresuid failed"); } if (NeedsNoRandomizeWorkaround()) { @@ -550,8 +547,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra se_info = new ScopedUtfChars(env, java_se_info); se_info_c_str = se_info->c_str(); if (se_info_c_str == NULL) { - ALOGE("se_info_c_str == NULL"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "se_info_c_str == NULL"); } } const char* se_name_c_str = NULL; @@ -560,15 +556,14 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra se_name = new ScopedUtfChars(env, java_se_name); se_name_c_str = se_name->c_str(); if (se_name_c_str == NULL) { - ALOGE("se_name_c_str == NULL"); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "se_name_c_str == NULL"); } } rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str); if (rc == -1) { ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid, is_system_server, se_info_c_str, se_name_c_str); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed"); } // Make it easier to debug audit logs by setting the main thread's name to the @@ -588,8 +583,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags, is_system_server ? NULL : instructionSet); if (env->ExceptionCheck()) { - ALOGE("Error calling post fork hooks."); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "Error calling post fork hooks."); } } else if (pid > 0) { // the parent process @@ -641,7 +635,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( int status; if (waitpid(pid, &status, WNOHANG) == pid) { ALOGE("System server process %d has died. Restarting Zygote!", pid); - RuntimeAbort(env); + RuntimeAbort(env, __LINE__, "System server process has died. Restarting Zygote!"); } } return pid; diff --git a/core/res/res/drawable/non_client_decor_title.xml b/core/res/res/drawable/decor_caption_title.xml index e50daea73b22..591605d33fae 100644 --- a/core/res/res/drawable/non_client_decor_title.xml +++ b/core/res/res/drawable/decor_caption_title.xml @@ -16,6 +16,6 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_window_focused="true" - android:drawable="@drawable/non_client_decor_title_focused" /> - <item android:drawable="@drawable/non_client_decor_title_unfocused" /> + android:drawable="@drawable/decor_caption_title_focused" /> + <item android:drawable="@drawable/decor_caption_title_unfocused" /> </selector> diff --git a/core/res/res/drawable/non_client_decor_title_focused.xml b/core/res/res/drawable/decor_caption_title_focused.xml index 7d1c23052bdb..7d1c23052bdb 100644 --- a/core/res/res/drawable/non_client_decor_title_focused.xml +++ b/core/res/res/drawable/decor_caption_title_focused.xml diff --git a/core/res/res/drawable/non_client_decor_title_unfocused.xml b/core/res/res/drawable/decor_caption_title_unfocused.xml index 2846d8ca6baa..2846d8ca6baa 100644 --- a/core/res/res/drawable/non_client_decor_title_unfocused.xml +++ b/core/res/res/drawable/decor_caption_title_unfocused.xml diff --git a/core/res/res/layout/non_client_decor_dark.xml b/core/res/res/layout/decor_caption_dark.xml index 40b8960a7ff9..273264d3d85f 100644 --- a/core/res/res/layout/non_client_decor_dark.xml +++ b/core/res/res/layout/decor_caption_dark.xml @@ -17,16 +17,17 @@ */ --> -<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="beforeDescendants" > <LinearLayout + android:id="@+id/caption" android:layout_width="match_parent" android:layout_gravity="end" android:layout_height="wrap_content" - android:background="@drawable/non_client_decor_title" + android:background="@drawable/decor_caption_title" android:focusable="false" android:descendantFocusability="blocksDescendants" > <LinearLayout @@ -53,4 +54,4 @@ android:contentDescription="@string/close_button_text" android:background="@drawable/decor_close_button_dark" /> </LinearLayout> -</com.android.internal.widget.NonClientDecorView> +</com.android.internal.widget.DecorCaptionView> diff --git a/core/res/res/layout/non_client_decor_light.xml b/core/res/res/layout/decor_caption_light.xml index c75d5261f6d8..fd9198ece271 100644 --- a/core/res/res/layout/non_client_decor_light.xml +++ b/core/res/res/layout/decor_caption_light.xml @@ -17,16 +17,17 @@ */ --> -<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="beforeDescendants" > <LinearLayout + android:id="@+id/caption" android:layout_width="match_parent" android:layout_gravity="end" android:layout_height="wrap_content" - android:background="@drawable/non_client_decor_title" + android:background="@drawable/decor_caption_title" android:focusable="false" android:descendantFocusability="blocksDescendants" > <LinearLayout @@ -53,4 +54,4 @@ android:contentDescription="@string/close_button_text" android:background="@drawable/decor_close_button_light" /> </LinearLayout> -</com.android.internal.widget.NonClientDecorView> +</com.android.internal.widget.DecorCaptionView> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b87d9e2e6959..057790a4cb08 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1091,6 +1091,14 @@ This feature should be disabled for most devices. --> <integer name="config_virtualKeyQuietTimeMillis">0</integer> + <!-- A list of potential packages, in priority order, that may contain an + ephemeral resolver. Each package will be be queried for a component + that has been granted the PACKAGE_EPHEMERAL_AGENT permission. + This may be empty if ephemeral apps are not supported. --> + <string-array name="config_ephemeralResolverPackage" translatable="false"> + <!-- Add packages here --> + </string-array> + <!-- Component name of the default wallpaper. This will be ImageWallpaper if not specified --> <string name="default_wallpaper_component" translatable="false">@null</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 556467a55ed7..1e325b114190 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -628,6 +628,7 @@ <java-symbol type="string" name="widget_default_package_name" /> <java-symbol type="string" name="widget_default_class_name" /> <java-symbol type="string" name="emergency_calls_only" /> + <java-symbol type="array" name="config_ephemeralResolverPackage" /> <java-symbol type="string" name="enable_accessibility_canceled" /> <java-symbol type="string" name="eventTypeAnniversary" /> <java-symbol type="string" name="eventTypeBirthday" /> @@ -1957,9 +1958,9 @@ <java-symbol type="id" name="maximize_window" /> <java-symbol type="id" name="close_window" /> <java-symbol type="id" name="client_decor_placeholder" /> - <java-symbol type="layout" name="non_client_decor_light" /> - <java-symbol type="layout" name="non_client_decor_dark" /> - <java-symbol type="drawable" name="non_client_decor_title_focused" /> + <java-symbol type="layout" name="decor_caption_light" /> + <java-symbol type="layout" name="decor_caption_dark" /> + <java-symbol type="drawable" name="decor_caption_title_focused" /> <!-- From TelephonyProvider --> <java-symbol type="xml" name="apns" /> diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd index 6e22be8e2f56..b8c105dceddf 100644 --- a/docs/html/guide/components/services.jd +++ b/docs/html/guide/components/services.jd @@ -512,7 +512,7 @@ android.content.Intent}. (You should never call {@link android.app.Service#onSta onStartCommand()} directly.)</p> <p>For example, an activity can start the example service in the previous section ({@code -HelloSevice}) using an explicit intent with {@link android.content.Context#startService +HelloService}) using an explicit intent with {@link android.content.Context#startService startService()}:</p> <pre> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index a50c945ca4f6..0ee877e28c2c 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -1424,7 +1424,7 @@ public abstract class Drawable { } } - static int resolveDensity(@NonNull Resources r, int parentDensity) { + static int resolveDensity(@Nullable Resources r, int parentDensity) { final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi; return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index eee9b24437b7..f630055e2a48 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -231,6 +231,9 @@ public class VectorDrawable extends Drawable { private int mDpiScaledHeight = 0; private Insets mDpiScaledInsets = Insets.NONE; + /** Whether DPI-scaled width, height, and insets need to be updated. */ + private boolean mDpiScaledDirty = true; + // Temp variable, only for saving "new" operation at the draw() time. private final float[] mTmpFloats = new float[9]; private final Matrix mTmpMatrix = new Matrix(); @@ -259,9 +262,13 @@ public class VectorDrawable extends Drawable { * displayed, or {@code null} to use the constant state defaults */ private void updateLocalState(Resources res) { - mTargetDensity = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mSourceDensity); + final int density = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mDensity); + if (mTargetDensity != density) { + mTargetDensity = density; + mDpiScaledDirty = true; + } + mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode); - computeVectorSize(); } @Override @@ -422,17 +429,26 @@ public class VectorDrawable extends Drawable { @Override public int getIntrinsicWidth() { + if (mDpiScaledDirty) { + computeVectorSize(); + } return mDpiScaledWidth; } @Override public int getIntrinsicHeight() { + if (mDpiScaledDirty) { + computeVectorSize(); + } return mDpiScaledHeight; } /** @hide */ @Override public Insets getOpticalInsets() { + if (mDpiScaledDirty) { + computeVectorSize(); + } return mDpiScaledInsets; } @@ -444,7 +460,7 @@ public class VectorDrawable extends Drawable { final VPathRenderer pathRenderer = mVectorState.mVPathRenderer; final Insets opticalInsets = pathRenderer.mOpticalInsets; - final int sourceDensity = pathRenderer.mSourceDensity; + final int sourceDensity = pathRenderer.mDensity; final int targetDensity = mTargetDensity; if (targetDensity != sourceDensity) { mDpiScaledWidth = Drawable.scaleFromDensity( @@ -465,6 +481,8 @@ public class VectorDrawable extends Drawable { mDpiScaledHeight = (int) pathRenderer.mBaseHeight; mDpiScaledInsets = opticalInsets; } + + mDpiScaledDirty = false; } @Override @@ -481,6 +499,11 @@ public class VectorDrawable extends Drawable { return; } + final VPathRenderer path = state.mVPathRenderer; + final boolean changedDensity = path.setDensity( + Drawable.resolveDensity(t.getResources(), 0)); + mDpiScaledDirty |= changedDensity; + if (state.mThemeAttrs != null) { final TypedArray a = t.resolveAttributes( state.mThemeAttrs, R.styleable.VectorDrawable); @@ -492,6 +515,9 @@ public class VectorDrawable extends Drawable { } finally { a.recycle(); } + + // May have changed size. + mDpiScaledDirty = true; } // Apply theme to contained color state list. @@ -499,7 +525,6 @@ public class VectorDrawable extends Drawable { state.mTint = state.mTint.obtainForTheme(t); } - final VPathRenderer path = state.mVPathRenderer; if (path != null && path.canApplyTheme()) { path.applyTheme(t); } @@ -565,21 +590,24 @@ public class VectorDrawable extends Drawable { } @Override - public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { final VectorDrawableState state = mVectorState; - final VPathRenderer pathRenderer = new VPathRenderer(); - state.mVPathRenderer = pathRenderer; + state.mVPathRenderer = new VPathRenderer(); + state.mVPathRenderer.setDensity(Drawable.resolveDensity(r, 0)); - final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable); updateStateFromTypedArray(a); a.recycle(); + mDpiScaledDirty = true; + state.mCacheDirty = true; - inflateInternal(res, parser, attrs, theme); + inflateChildElements(r, parser, attrs, theme); // Update local properties. - updateLocalState(res); + updateLocalState(r); } private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { @@ -592,13 +620,6 @@ public class VectorDrawable extends Drawable { // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); - // The density may have changed since the last update (if any). Any - // dimension-type attributes will need their default values scaled. - final int targetDensity = Drawable.resolveDensity(a.getResources(), 0); - final int sourceDensity = pathRenderer.mSourceDensity; - final float densityScale = targetDensity / (float) sourceDensity; - pathRenderer.mSourceDensity = targetDensity; - final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1); if (tintMode != -1) { state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); @@ -626,11 +647,9 @@ public class VectorDrawable extends Drawable { } pathRenderer.mBaseWidth = a.getDimension( - R.styleable.VectorDrawable_width, - pathRenderer.mBaseWidth * densityScale); + R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth); pathRenderer.mBaseHeight = a.getDimension( - R.styleable.VectorDrawable_height, - pathRenderer.mBaseHeight * densityScale); + R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight); if (pathRenderer.mBaseWidth <= 0) { throw new XmlPullParserException(a.getPositionDescription() + @@ -641,21 +660,17 @@ public class VectorDrawable extends Drawable { } final int insetLeft = a.getDimensionPixelOffset( - R.styleable.VectorDrawable_opticalInsetLeft, - (int) (pathRenderer.mOpticalInsets.left * densityScale)); + R.styleable.VectorDrawable_opticalInsetLeft, pathRenderer.mOpticalInsets.left); final int insetTop = a.getDimensionPixelOffset( - R.styleable.VectorDrawable_opticalInsetTop, - (int) (pathRenderer.mOpticalInsets.top * densityScale)); + R.styleable.VectorDrawable_opticalInsetTop, pathRenderer.mOpticalInsets.top); final int insetRight = a.getDimensionPixelOffset( - R.styleable.VectorDrawable_opticalInsetRight, - (int) (pathRenderer.mOpticalInsets.right * densityScale)); + R.styleable.VectorDrawable_opticalInsetRight, pathRenderer.mOpticalInsets.right); final int insetBottom = a.getDimensionPixelOffset( - R.styleable.VectorDrawable_opticalInsetBottom, - (int) (pathRenderer.mOpticalInsets.bottom * densityScale)); + R.styleable.VectorDrawable_opticalInsetBottom, pathRenderer.mOpticalInsets.bottom); pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); - final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha, - pathRenderer.getAlpha()); + final float alphaInFloat = a.getFloat( + R.styleable.VectorDrawable_alpha, pathRenderer.getAlpha()); pathRenderer.setAlpha(alphaInFloat); final String name = a.getString(R.styleable.VectorDrawable_name); @@ -665,7 +680,7 @@ public class VectorDrawable extends Drawable { } } - private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs, + private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final VectorDrawableState state = mVectorState; final VPathRenderer pathRenderer = state.mVPathRenderer; @@ -929,7 +944,7 @@ public class VectorDrawable extends Drawable { int mRootAlpha = 0xFF; String mRootName = null; - int mSourceDensity = DisplayMetrics.DENSITY_DEFAULT; + int mDensity = DisplayMetrics.DENSITY_DEFAULT; final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>(); @@ -966,12 +981,37 @@ public class VectorDrawable extends Drawable { mChangingConfigurations = copy.mChangingConfigurations; mRootAlpha = copy.mRootAlpha; mRootName = copy.mRootName; - mSourceDensity = copy.mSourceDensity; + mDensity = copy.mDensity; if (copy.mRootName != null) { mVGTargetsMap.put(copy.mRootName, this); } } + public final boolean setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + applyDensityScaling(sourceDensity, targetDensity); + return true; + } + return false; + } + + private void applyDensityScaling(int sourceDensity, int targetDensity) { + mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity); + mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity); + + final int insetLeft = Drawable.scaleFromDensity( + mOpticalInsets.left, sourceDensity, targetDensity, false); + final int insetTop = Drawable.scaleFromDensity( + mOpticalInsets.top, sourceDensity, targetDensity, false); + final int insetRight = Drawable.scaleFromDensity( + mOpticalInsets.right, sourceDensity, targetDensity, false); + final int insetBottom = Drawable.scaleFromDensity( + mOpticalInsets.bottom, sourceDensity, targetDensity, false); + mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); + } + public boolean canApplyTheme() { return mRootGroup.canApplyTheme(); } @@ -1321,7 +1361,7 @@ public class VectorDrawable extends Drawable { * Common Path information for clip path and normal path. */ private static abstract class VPath implements VObject { - protected PathParser.PathDataNode[] mNodes = null; + protected PathParser.PathData mPathData = null; String mPathName; int mChangingConfigurations; @@ -1332,7 +1372,7 @@ public class VectorDrawable extends Drawable { public VPath(VPath copy) { mPathName = copy.mPathName; mChangingConfigurations = copy.mChangingConfigurations; - mNodes = PathParser.deepCopyNodes(copy.mNodes); + mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData); } public String getPathName() { @@ -1345,18 +1385,14 @@ public class VectorDrawable extends Drawable { /* Setters and Getters, used by animator from AnimatedVectorDrawable. */ @SuppressWarnings("unused") - public PathParser.PathDataNode[] getPathData() { - return mNodes; + public PathParser.PathData getPathData() { + return mPathData; } + // TODO: Move the PathEvaluator and this setter and the getter above into native. @SuppressWarnings("unused") - public void setPathData(PathParser.PathDataNode[] nodes) { - if (!PathParser.canMorph(mNodes, nodes)) { - // This should not happen in the middle of animation. - mNodes = PathParser.deepCopyNodes(nodes); - } else { - PathParser.updateNodes(mNodes, nodes); - } + public void setPathData(PathParser.PathData pathData) { + mPathData.setPathData(pathData); } @Override @@ -1392,8 +1428,8 @@ public class VectorDrawable extends Drawable { * @param outPath the output path */ protected void toPath(TempState temp, Path outPath) { - if (mNodes != null) { - PathParser.PathDataNode.nodesToPath(mNodes, outPath); + if (mPathData != null) { + PathParser.createPathFromPathData(outPath, mPathData); } } @@ -1488,9 +1524,9 @@ public class VectorDrawable extends Drawable { mPathName = pathName; } - final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData); - if (pathData != null) { - mNodes = PathParser.createNodesFromPathData(pathData); + final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData); + if (pathDataString != null) { + mPathData = new PathParser.PathData(pathDataString); } } @@ -1719,9 +1755,9 @@ public class VectorDrawable extends Drawable { mPathName = pathName; } - final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData); - if (pathData != null) { - mNodes = PathParser.createNodesFromPathData(pathData); + final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData); + if (pathString != null) { + mPathData = new PathParser.PathData(pathString); } final ColorStateList fillColors = a.getColorStateList( diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 8565372f3b53..0a57d509d129 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -29,6 +29,8 @@ hwui_src_files := \ utils/NinePatchImpl.cpp \ utils/StringUtils.cpp \ utils/TestWindowContext.cpp \ + utils/VectorDrawableUtils.cpp \ + utils/TestUtils.cpp \ AmbientShadow.cpp \ AnimationContext.cpp \ Animator.cpp \ @@ -218,7 +220,7 @@ LOCAL_SRC_FILES += \ unit_tests/FatVectorTests.cpp \ unit_tests/LayerUpdateQueueTests.cpp \ unit_tests/LinearAllocatorTests.cpp \ - unit_tests/PathParserTests.cpp \ + unit_tests/VectorDrawableTests.cpp \ unit_tests/OffscreenBufferPoolTests.cpp \ unit_tests/StringUtilsTests.cpp @@ -252,9 +254,11 @@ LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static LOCAL_SRC_FILES += \ tests/TestContext.cpp \ - tests/TreeContentAnimation.cpp \ + tests/TestSceneRunner.cpp \ tests/main.cpp +LOCAL_SRC_FILES += $(call all-cpp-files-under, tests/scenes) + include $(BUILD_EXECUTABLE) # ------------------------ diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp index 35230fff279e..4e9ac9c7f723 100644 --- a/libs/hwui/PathParser.cpp +++ b/libs/hwui/PathParser.cpp @@ -221,7 +221,7 @@ void PathParser::parseStringForSkPath(SkPath* skPath, ParseResult* result, const result->failureMessage = "No verbs found in the string for pathData"; return; } - VectorDrawablePath::verbsToPath(skPath, &pathData); + VectorDrawableUtils::verbsToPath(skPath, pathData); return; } diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h index a9c1e60dae2e..4c87b1898ae5 100644 --- a/libs/hwui/PathParser.h +++ b/libs/hwui/PathParser.h @@ -18,6 +18,7 @@ #define ANDROID_HWUI_PATHPARSER_H #include "VectorDrawablePath.h" +#include "utils/VectorDrawableUtils.h" #include <jni.h> #include <android/log.h> @@ -40,7 +41,7 @@ public: */ ANDROID_API static void parseStringForSkPath(SkPath* outPath, ParseResult* result, const char* pathStr, size_t strLength); - static void getPathDataFromString(PathData* outData, ParseResult* result, + ANDROID_API static void getPathDataFromString(PathData* outData, ParseResult* result, const char* pathStr, size_t strLength); static void dump(const PathData& data); }; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 716d5360c25c..3f24f44e52da 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -288,6 +288,9 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { bool transformUpdateNeeded = false; if (!mLayer) { mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight()); +#if !HWUI_NEW_OPS + applyLayerPropertiesToLayer(info); +#endif damageSelf(info); transformUpdateNeeded = true; } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) { diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp index 05ea2da88fdb..c9a54ca870fa 100644 --- a/libs/hwui/VectorDrawablePath.cpp +++ b/libs/hwui/VectorDrawablePath.cpp @@ -17,6 +17,7 @@ #include "VectorDrawablePath.h" #include "PathParser.h" +#include "utils/VectorDrawableUtils.h" #include <math.h> #include <utils/Log.h> @@ -24,476 +25,35 @@ namespace android { namespace uirenderer { -class PathResolver { -public: - float currentX = 0; - float currentY = 0; - float ctrlPointX = 0; - float ctrlPointY = 0; - float currentSegmentStartX = 0; - float currentSegmentStartY = 0; - void addCommand(SkPath* outPath, char previousCmd, - char cmd, const std::vector<float>* points, size_t start, size_t end); -}; VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) { PathParser::ParseResult result; PathParser::getPathDataFromString(&mData, &result, pathStr, strLength); if (!result.failureOccurred) { - verbsToPath(&mSkPath, &mData); + VectorDrawableUtils::verbsToPath(&mSkPath, mData); } } VectorDrawablePath::VectorDrawablePath(const PathData& data) { mData = data; // Now we need to construct a path - verbsToPath(&mSkPath, &data); + VectorDrawableUtils::verbsToPath(&mSkPath, data); } VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) { mData = path.mData; - verbsToPath(&mSkPath, &mData); + VectorDrawableUtils::verbsToPath(&mSkPath, mData); } -bool VectorDrawablePath::canMorph(const PathData& morphTo) { - if (mData.verbs.size() != morphTo.verbs.size()) { - return false; - } - for (unsigned int i = 0; i < mData.verbs.size(); i++) { - if (mData.verbs[i] != morphTo.verbs[i] - || mData.verbSizes[i] != morphTo.verbSizes[i]) { - return false; - } - } - return true; +bool VectorDrawablePath::canMorph(const PathData& morphTo) { + return VectorDrawableUtils::canMorph(mData, morphTo); } bool VectorDrawablePath::canMorph(const VectorDrawablePath& path) { return canMorph(path.mData); } - /** - * Convert an array of PathVerb to Path. - */ -void VectorDrawablePath::verbsToPath(SkPath* outPath, const PathData* data) { - PathResolver resolver; - char previousCommand = 'm'; - size_t start = 0; - outPath->reset(); - for (unsigned int i = 0; i < data->verbs.size(); i++) { - size_t verbSize = data->verbSizes[i]; - resolver.addCommand(outPath, previousCommand, data->verbs[i], &data->points, start, - start + verbSize); - previousCommand = data->verbs[i]; - start += verbSize; - } -} - -/** - * The current PathVerb will be interpolated between the - * <code>nodeFrom</code> and <code>nodeTo</code> according to the - * <code>fraction</code>. - * - * @param nodeFrom The start value as a PathVerb. - * @param nodeTo The end value as a PathVerb - * @param fraction The fraction to interpolate. - */ -void VectorDrawablePath::interpolatePaths(PathData* outData, - const PathData* from, const PathData* to, float fraction) { - outData->points.resize(from->points.size()); - outData->verbSizes = from->verbSizes; - outData->verbs = from->verbs; - - for (size_t i = 0; i < from->points.size(); i++) { - outData->points[i] = from->points[i] * (1 - fraction) + to->points[i] * fraction; - } -} - -/** - * Converts an arc to cubic Bezier segments and records them in p. - * - * @param p The target for the cubic Bezier segments - * @param cx The x coordinate center of the ellipse - * @param cy The y coordinate center of the ellipse - * @param a The radius of the ellipse in the horizontal direction - * @param b The radius of the ellipse in the vertical direction - * @param e1x E(eta1) x coordinate of the starting point of the arc - * @param e1y E(eta2) y coordinate of the starting point of the arc - * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane - * @param start The start angle of the arc on the ellipse - * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse - */ -static void arcToBezier(SkPath* p, - double cx, - double cy, - double a, - double b, - double e1x, - double e1y, - double theta, - double start, - double sweep) { - // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html - // and http://www.spaceroots.org/documents/ellipse/node22.html - - // Maximum of 45 degrees per cubic Bezier segment - int numSegments = ceil(fabs(sweep * 4 / M_PI)); - - double eta1 = start; - double cosTheta = cos(theta); - double sinTheta = sin(theta); - double cosEta1 = cos(eta1); - double sinEta1 = sin(eta1); - double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); - double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); - - double anglePerSegment = sweep / numSegments; - for (int i = 0; i < numSegments; i++) { - double eta2 = eta1 + anglePerSegment; - double sinEta2 = sin(eta2); - double cosEta2 = cos(eta2); - double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); - double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); - double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; - double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; - double tanDiff2 = tan((eta2 - eta1) / 2); - double alpha = - sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; - double q1x = e1x + alpha * ep1x; - double q1y = e1y + alpha * ep1y; - double q2x = e2x - alpha * ep2x; - double q2y = e2y - alpha * ep2y; - - p->cubicTo((float) q1x, - (float) q1y, - (float) q2x, - (float) q2y, - (float) e2x, - (float) e2y); - eta1 = eta2; - e1x = e2x; - e1y = e2y; - ep1x = ep2x; - ep1y = ep2y; - } -} -inline double toRadians(float theta) { return theta * M_PI / 180;} - -static void drawArc(SkPath* p, - float x0, - float y0, - float x1, - float y1, - float a, - float b, - float theta, - bool isMoreThanHalf, - bool isPositiveArc) { - - /* Convert rotation angle from degrees to radians */ - double thetaD = toRadians(theta); - /* Pre-compute rotation matrix entries */ - double cosTheta = cos(thetaD); - double sinTheta = sin(thetaD); - /* Transform (x0, y0) and (x1, y1) into unit space */ - /* using (inverse) rotation, followed by (inverse) scale */ - double x0p = (x0 * cosTheta + y0 * sinTheta) / a; - double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; - double x1p = (x1 * cosTheta + y1 * sinTheta) / a; - double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; - - /* Compute differences and averages */ - double dx = x0p - x1p; - double dy = y0p - y1p; - double xm = (x0p + x1p) / 2; - double ym = (y0p + y1p) / 2; - /* Solve for intersecting unit circles */ - double dsq = dx * dx + dy * dy; - if (dsq == 0.0) { - ALOGW("Points are coincident"); - return; /* Points are coincident */ - } - double disc = 1.0 / dsq - 1.0 / 4.0; - if (disc < 0.0) { - ALOGW("Points are too far apart %f", dsq); - float adjust = (float) (sqrt(dsq) / 1.99999); - drawArc(p, x0, y0, x1, y1, a * adjust, - b * adjust, theta, isMoreThanHalf, isPositiveArc); - return; /* Points are too far apart */ - } - double s = sqrt(disc); - double sdx = s * dx; - double sdy = s * dy; - double cx; - double cy; - if (isMoreThanHalf == isPositiveArc) { - cx = xm - sdy; - cy = ym + sdx; - } else { - cx = xm + sdy; - cy = ym - sdx; - } - - double eta0 = atan2((y0p - cy), (x0p - cx)); - - double eta1 = atan2((y1p - cy), (x1p - cx)); - - double sweep = (eta1 - eta0); - if (isPositiveArc != (sweep >= 0)) { - if (sweep > 0) { - sweep -= 2 * M_PI; - } else { - sweep += 2 * M_PI; - } - } - - cx *= a; - cy *= b; - double tcx = cx; - cx = cx * cosTheta - cy * sinTheta; - cy = tcx * sinTheta + cy * cosTheta; - - arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); -} - -// Use the given verb, and points in the range [start, end) to insert a command into the SkPath. -void PathResolver::addCommand(SkPath* outPath, char previousCmd, - char cmd, const std::vector<float>* points, size_t start, size_t end) { - - int incr = 2; - float reflectiveCtrlPointX; - float reflectiveCtrlPointY; - - switch (cmd) { - case 'z': - case 'Z': - outPath->close(); - // Path is closed here, but we need to move the pen to the - // closed position. So we cache the segment's starting position, - // and restore it here. - currentX = currentSegmentStartX; - currentY = currentSegmentStartY; - ctrlPointX = currentSegmentStartX; - ctrlPointY = currentSegmentStartY; - outPath->moveTo(currentX, currentY); - break; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - incr = 2; - break; - case 'h': - case 'H': - case 'v': - case 'V': - incr = 1; - break; - case 'c': - case 'C': - incr = 6; - break; - case 's': - case 'S': - case 'q': - case 'Q': - incr = 4; - break; - case 'a': - case 'A': - incr = 7; - break; - } - - for (unsigned int k = start; k < end; k += incr) { - switch (cmd) { - case 'm': // moveto - Start a new sub-path (relative) - currentX += points->at(k + 0); - currentY += points->at(k + 1); - if (k > start) { - // According to the spec, if a moveto is followed by multiple - // pairs of coordinates, the subsequent pairs are treated as - // implicit lineto commands. - outPath->rLineTo(points->at(k + 0), points->at(k + 1)); - } else { - outPath->rMoveTo(points->at(k + 0), points->at(k + 1)); - currentSegmentStartX = currentX; - currentSegmentStartY = currentY; - } - break; - case 'M': // moveto - Start a new sub-path - currentX = points->at(k + 0); - currentY = points->at(k + 1); - if (k > start) { - // According to the spec, if a moveto is followed by multiple - // pairs of coordinates, the subsequent pairs are treated as - // implicit lineto commands. - outPath->lineTo(points->at(k + 0), points->at(k + 1)); - } else { - outPath->moveTo(points->at(k + 0), points->at(k + 1)); - currentSegmentStartX = currentX; - currentSegmentStartY = currentY; - } - break; - case 'l': // lineto - Draw a line from the current point (relative) - outPath->rLineTo(points->at(k + 0), points->at(k + 1)); - currentX += points->at(k + 0); - currentY += points->at(k + 1); - break; - case 'L': // lineto - Draw a line from the current point - outPath->lineTo(points->at(k + 0), points->at(k + 1)); - currentX = points->at(k + 0); - currentY = points->at(k + 1); - break; - case 'h': // horizontal lineto - Draws a horizontal line (relative) - outPath->rLineTo(points->at(k + 0), 0); - currentX += points->at(k + 0); - break; - case 'H': // horizontal lineto - Draws a horizontal line - outPath->lineTo(points->at(k + 0), currentY); - currentX = points->at(k + 0); - break; - case 'v': // vertical lineto - Draws a vertical line from the current point (r) - outPath->rLineTo(0, points->at(k + 0)); - currentY += points->at(k + 0); - break; - case 'V': // vertical lineto - Draws a vertical line from the current point - outPath->lineTo(currentX, points->at(k + 0)); - currentY = points->at(k + 0); - break; - case 'c': // curveto - Draws a cubic Bézier curve (relative) - outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), - points->at(k + 4), points->at(k + 5)); - - ctrlPointX = currentX + points->at(k + 2); - ctrlPointY = currentY + points->at(k + 3); - currentX += points->at(k + 4); - currentY += points->at(k + 5); - - break; - case 'C': // curveto - Draws a cubic Bézier curve - outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), - points->at(k + 4), points->at(k + 5)); - currentX = points->at(k + 4); - currentY = points->at(k + 5); - ctrlPointX = points->at(k + 2); - ctrlPointY = points->at(k + 3); - break; - case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) - reflectiveCtrlPointX = 0; - reflectiveCtrlPointY = 0; - if (previousCmd == 'c' || previousCmd == 's' - || previousCmd == 'C' || previousCmd == 'S') { - reflectiveCtrlPointX = currentX - ctrlPointX; - reflectiveCtrlPointY = currentY - ctrlPointY; - } - outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - points->at(k + 0), points->at(k + 1), - points->at(k + 2), points->at(k + 3)); - ctrlPointX = currentX + points->at(k + 0); - ctrlPointY = currentY + points->at(k + 1); - currentX += points->at(k + 2); - currentY += points->at(k + 3); - break; - case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) - reflectiveCtrlPointX = currentX; - reflectiveCtrlPointY = currentY; - if (previousCmd == 'c' || previousCmd == 's' - || previousCmd == 'C' || previousCmd == 'S') { - reflectiveCtrlPointX = 2 * currentX - ctrlPointX; - reflectiveCtrlPointY = 2 * currentY - ctrlPointY; - } - outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); - ctrlPointX = points->at(k + 0); - ctrlPointY = points->at(k + 1); - currentX = points->at(k + 2); - currentY = points->at(k + 3); - break; - case 'q': // Draws a quadratic Bézier (relative) - outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); - ctrlPointX = currentX + points->at(k + 0); - ctrlPointY = currentY + points->at(k + 1); - currentX += points->at(k + 2); - currentY += points->at(k + 3); - break; - case 'Q': // Draws a quadratic Bézier - outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); - ctrlPointX = points->at(k + 0); - ctrlPointY = points->at(k + 1); - currentX = points->at(k + 2); - currentY = points->at(k + 3); - break; - case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) - reflectiveCtrlPointX = 0; - reflectiveCtrlPointY = 0; - if (previousCmd == 'q' || previousCmd == 't' - || previousCmd == 'Q' || previousCmd == 'T') { - reflectiveCtrlPointX = currentX - ctrlPointX; - reflectiveCtrlPointY = currentY - ctrlPointY; - } - outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - points->at(k + 0), points->at(k + 1)); - ctrlPointX = currentX + reflectiveCtrlPointX; - ctrlPointY = currentY + reflectiveCtrlPointY; - currentX += points->at(k + 0); - currentY += points->at(k + 1); - break; - case 'T': // Draws a quadratic Bézier curve (reflective control point) - reflectiveCtrlPointX = currentX; - reflectiveCtrlPointY = currentY; - if (previousCmd == 'q' || previousCmd == 't' - || previousCmd == 'Q' || previousCmd == 'T') { - reflectiveCtrlPointX = 2 * currentX - ctrlPointX; - reflectiveCtrlPointY = 2 * currentY - ctrlPointY; - } - outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, - points->at(k + 0), points->at(k + 1)); - ctrlPointX = reflectiveCtrlPointX; - ctrlPointY = reflectiveCtrlPointY; - currentX = points->at(k + 0); - currentY = points->at(k + 1); - break; - case 'a': // Draws an elliptical arc - // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) - drawArc(outPath, - currentX, - currentY, - points->at(k + 5) + currentX, - points->at(k + 6) + currentY, - points->at(k + 0), - points->at(k + 1), - points->at(k + 2), - points->at(k + 3) != 0, - points->at(k + 4) != 0); - currentX += points->at(k + 5); - currentY += points->at(k + 6); - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - case 'A': // Draws an elliptical arc - drawArc(outPath, - currentX, - currentY, - points->at(k + 5), - points->at(k + 6), - points->at(k + 0), - points->at(k + 1), - points->at(k + 2), - points->at(k + 3) != 0, - points->at(k + 4) != 0); - currentX = points->at(k + 5); - currentY = points->at(k + 6); - ctrlPointX = currentX; - ctrlPointY = currentY; - break; - } - previousCmd = cmd; - } -} }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/VectorDrawablePath.h b/libs/hwui/VectorDrawablePath.h index 40ce986b1a57..2e56349b3aa4 100644 --- a/libs/hwui/VectorDrawablePath.h +++ b/libs/hwui/VectorDrawablePath.h @@ -17,15 +17,14 @@ #ifndef ANDROID_HWUI_VPATH_H #define ANDROID_HWUI_VPATH_H +#include <cutils/compiler.h> #include "SkPath.h" #include <vector> namespace android { namespace uirenderer { - - -struct PathData { +struct ANDROID_API PathData { // TODO: Try using FatVector instead of std::vector and do a micro benchmark on the performance // difference. std::vector<char> verbs; @@ -44,9 +43,7 @@ public: VectorDrawablePath(const char* path, size_t strLength); bool canMorph(const PathData& path); bool canMorph(const VectorDrawablePath& path); - static void verbsToPath(SkPath* outPath, const PathData* data); - static void interpolatePaths(PathData* outPathData, const PathData* from, const PathData* to, - float fraction); + private: PathData mData; SkPath mSkPath; diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp index 7a620379d7b7..4be1f992e399 100644 --- a/libs/hwui/microbench/DisplayListCanvasBench.cpp +++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp @@ -23,7 +23,7 @@ #include "DisplayListCanvas.h" #endif #include "microbench/MicroBench.h" -#include "unit_tests/TestUtils.h" +#include "utils/TestUtils.h" using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp index b24858e73525..eea0c7f9ccbe 100644 --- a/libs/hwui/microbench/OpReordererBench.cpp +++ b/libs/hwui/microbench/OpReordererBench.cpp @@ -21,7 +21,7 @@ #include "OpReorderer.h" #include "RecordedOp.h" #include "RecordingCanvas.h" -#include "unit_tests/TestUtils.h" +#include "utils/TestUtils.h" #include "microbench/MicroBench.h" #include <vector> diff --git a/libs/hwui/microbench/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp index 171078db9ef6..3d9fafac6c93 100644 --- a/libs/hwui/microbench/PathParserBench.cpp +++ b/libs/hwui/microbench/PathParserBench.cpp @@ -17,21 +17,35 @@ #include <benchmark/Benchmark.h> #include "PathParser.h" +#include "VectorDrawablePath.h" #include <SkPath.h> using namespace android; using namespace android::uirenderer; -BENCHMARK_NO_ARG(BM_PathParser_parseStringPath); -void BM_PathParser_parseStringPath::Run(int iter) { - const char* pathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10"; +static const char* sPathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10"; + +BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForSkPath); +void BM_PathParser_parseStringPathForSkPath::Run(int iter) { SkPath skPath; - size_t length = strlen(pathString); + size_t length = strlen(sPathString); + PathParser::ParseResult result; + StartBenchmarkTiming(); + for (int i = 0; i < iter; i++) { + PathParser::parseStringForSkPath(&skPath, &result, sPathString, length); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForPathData); +void BM_PathParser_parseStringPathForPathData::Run(int iter) { + size_t length = strlen(sPathString); + PathData outData; PathParser::ParseResult result; StartBenchmarkTiming(); for (int i = 0; i < iter; i++) { - PathParser::parseStringForSkPath(&skPath, &result, pathString, length); + PathParser::getPathDataFromString(&outData, &result, sPathString, length); } StopBenchmarkTiming(); } diff --git a/libs/hwui/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h index e16310e034be..3f87d7fc34ce 100644 --- a/libs/hwui/tests/Benchmark.h +++ b/libs/hwui/tests/Benchmark.h @@ -16,6 +16,8 @@ #ifndef TESTS_BENCHMARK_H #define TESTS_BENCHMARK_H +#include "TestScene.h" + #include <string> #include <vector> @@ -26,12 +28,17 @@ struct BenchmarkOptions { int count; }; -typedef void (*BenchmarkFunctor)(const BenchmarkOptions&); +typedef test::TestScene* (*CreateScene)(const BenchmarkOptions&); + +template <class T> +test::TestScene* simpleCreateScene(const BenchmarkOptions&) { + return new T(); +} struct BenchmarkInfo { std::string name; std::string description; - BenchmarkFunctor functor; + CreateScene createScene; }; class Benchmark { diff --git a/libs/hwui/tests/TestScene.h b/libs/hwui/tests/TestScene.h new file mode 100644 index 000000000000..b5d8954652a3 --- /dev/null +++ b/libs/hwui/tests/TestScene.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TESTS_TESTSCENE_H +#define TESTS_TESTSCENE_H + +namespace android { +namespace uirenderer { +class RenderNode; + +#if HWUI_NEW_OPS +class RecordingCanvas; +typedef RecordingCanvas TestCanvas; +#else +class DisplayListCanvas; +typedef DisplayListCanvas TestCanvas; +#endif + +namespace test { + +class TestScene { +public: + virtual ~TestScene() {} + virtual void createContent(int width, int height, TestCanvas& renderer) = 0; + virtual void doFrame(int frameNr) = 0; +}; + +} // namespace test +} // namespace uirenderer +} // namespace android + +#endif /* TESTS_TESTSCENE_H */ diff --git a/libs/hwui/tests/TestSceneRunner.cpp b/libs/hwui/tests/TestSceneRunner.cpp new file mode 100644 index 000000000000..0376e109052d --- /dev/null +++ b/libs/hwui/tests/TestSceneRunner.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AnimationContext.h" +#include "Benchmark.h" +#include "RenderNode.h" +#include "TestContext.h" +#include "scenes/TestSceneBase.h" +#include "renderthread/RenderProxy.h" +#include "renderthread/RenderTask.h" + +#include <cutils/log.h> +#include <gui/Surface.h> +#include <ui/PixelFormat.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +class ContextFactory : public IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { + return new AnimationContext(clock); + } +}; + +void run(const BenchmarkInfo& info, const BenchmarkOptions& opts) { + // Switch to the real display + gDisplay = getBuiltInDisplay(); + + std::unique_ptr<TestScene> scene(info.createScene(opts)); + + TestContext testContext; + + // create the native surface + const int width = gDisplay.w; + const int height = gDisplay.h; + sp<Surface> surface = testContext.surface(); + + sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height, + [&scene, width, height](RenderProperties& props, TestCanvas& canvas) { + props.setClipToBounds(false); + scene->createContent(width, height, canvas); + }); + + ContextFactory factory; + std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, + rootNode.get(), &factory)); + proxy->loadSystemProperties(); + proxy->initialize(surface); + float lightX = width / 2.0; + proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); + proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); + + // Do a few cold runs then reset the stats so that the caches are all hot + for (int i = 0; i < 3; i++) { + testContext.waitForVsync(); + nsecs_t vsync = systemTime(CLOCK_MONOTONIC); + UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync); + proxy->syncAndDrawFrame(); + } + proxy->resetProfileInfo(); + + for (int i = 0; i < opts.count; i++) { + testContext.waitForVsync(); + + ATRACE_NAME("UI-Draw Frame"); + nsecs_t vsync = systemTime(CLOCK_MONOTONIC); + UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync); + scene->doFrame(i); + proxy->syncAndDrawFrame(); + } + + proxy->dumpProfileInfo(STDOUT_FILENO, 0); +} diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp deleted file mode 100644 index 81bf9ed1cde3..000000000000 --- a/libs/hwui/tests/TreeContentAnimation.cpp +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <cutils/log.h> -#include <gui/Surface.h> -#include <ui/PixelFormat.h> - -#include <AnimationContext.h> -#include <DisplayListCanvas.h> -#include <RecordingCanvas.h> -#include <RenderNode.h> -#include <renderthread/RenderProxy.h> -#include <renderthread/RenderTask.h> -#include <unit_tests/TestUtils.h> - -#include "Benchmark.h" -#include "TestContext.h" - -#include "protos/hwui.pb.h" - -#include <stdio.h> -#include <unistd.h> -#include <getopt.h> -#include <vector> - -using namespace android; -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; -using namespace android::uirenderer::test; - -#if HWUI_NEW_OPS -typedef RecordingCanvas TestCanvas; -#else -typedef DisplayListCanvas TestCanvas; -#endif - - -class ContextFactory : public IContextFactory { -public: - virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { - return new AnimationContext(clock); - } -}; - -static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) { - TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight()); - contentCallback(canvas); - node.setStagingDisplayList(canvas.finishRecording()); -} - -class TreeContentAnimation { -public: - virtual ~TreeContentAnimation() {} - int frameCount = 150; - virtual int getFrameCount() { return frameCount; } - virtual void setFrameCount(int fc) { - if (fc > 0) { - frameCount = fc; - } - } - virtual void createContent(int width, int height, TestCanvas* canvas) = 0; - virtual void doFrame(int frameNr) = 0; - - template <class T> - static void run(const BenchmarkOptions& opts) { - // Switch to the real display - gDisplay = getBuiltInDisplay(); - - T animation; - animation.setFrameCount(opts.count); - - TestContext testContext; - - // create the native surface - const int width = gDisplay.w; - const int height = gDisplay.h; - sp<Surface> surface = testContext.surface(); - - RenderNode* rootNode = new RenderNode(); - rootNode->incStrong(nullptr); - rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height); - rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - rootNode->mutateStagingProperties().setClipToBounds(false); - rootNode->setPropertyFieldsDirty(RenderNode::GENERIC); - - ContextFactory factory; - std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory)); - proxy->loadSystemProperties(); - proxy->initialize(surface); - float lightX = width / 2.0; - proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); - proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); - - recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) { - animation.createContent(width, height, &canvas); //TODO: no& - }); - - // Do a few cold runs then reset the stats so that the caches are all hot - for (int i = 0; i < 3; i++) { - testContext.waitForVsync(); - proxy->syncAndDrawFrame(); - } - proxy->resetProfileInfo(); - - for (int i = 0; i < animation.getFrameCount(); i++) { - testContext.waitForVsync(); - - ATRACE_NAME("UI-Draw Frame"); - nsecs_t vsync = systemTime(CLOCK_MONOTONIC); - UiFrameInfoBuilder(proxy->frameInfo()) - .setVsync(vsync, vsync); - animation.doFrame(i); - proxy->syncAndDrawFrame(); - } - - proxy->dumpProfileInfo(STDOUT_FILENO, 0); - rootNode->decStrong(nullptr); - } -}; - -class ShadowGridAnimation : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* canvas) override { - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - canvas->insertReorderBarrier(true); - - for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { - for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { - sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); - canvas->drawRenderNode(card.get()); - cards.push_back(card); - } - } - - canvas->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(curFrame); - cards[ci]->mutateStagingProperties().setTranslationY(curFrame); - cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->mutateStagingProperties().setElevation(dp(16)); - node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1); - node->mutateStagingProperties().mutableOutline().setShouldClip(true); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - - recordNode(*node, [](TestCanvas& canvas) { - canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - }); - return node; - } -}; -static Benchmark _ShadowGrid(BenchmarkInfo{ - "shadowgrid", - "A grid of rounded rects that cast a shadow. Simplified scenario of an " - "Android TV-style launcher interface. High CPU/GPU load.", - TreeContentAnimation::run<ShadowGridAnimation> -}); - -class ShadowGrid2Animation : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* canvas) override { - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - canvas->insertReorderBarrier(true); - - for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { - for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { - sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); - canvas->drawRenderNode(card.get()); - cards.push_back(card); - } - } - - canvas->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(curFrame); - cards[ci]->mutateStagingProperties().setTranslationY(curFrame); - cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->mutateStagingProperties().setElevation(dp(16)); - node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); - node->mutateStagingProperties().mutableOutline().setShouldClip(true); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - - recordNode(*node, [](TestCanvas& canvas) { - canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - }); - return node; - } -}; -static Benchmark _ShadowGrid2(BenchmarkInfo{ - "shadowgrid2", - "A dense grid of rounded rects that cast a shadow. This is a higher CPU load " - "variant of shadowgrid. Very high CPU load, high GPU load.", - TreeContentAnimation::run<ShadowGrid2Animation> -}); - -class RectGridAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card = new RenderNode(); - void createContent(int width, int height, TestCanvas* canvas) override { - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - canvas->insertReorderBarrier(true); - - card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - recordNode(*card, [](TestCanvas& canvas) { - canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); - - SkRegion region; - for (int xOffset = 0; xOffset < 200; xOffset+=2) { - for (int yOffset = 0; yOffset < 200; yOffset+=2) { - region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); - } - } - - SkPaint paint; - paint.setColor(0xff00ffff); - canvas.drawRegion(region, paint); - }); - canvas->drawRenderNode(card.get()); - - canvas->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -}; -static Benchmark _RectGrid(BenchmarkInfo{ - "rectgrid", - "A dense grid of 1x1 rects that should visually look like a single rect. " - "Low CPU/GPU load.", - TreeContentAnimation::run<RectGridAnimation> -}); - -class OvalAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card = new RenderNode(); - void createContent(int width, int height, TestCanvas* canvas) override { - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - canvas->insertReorderBarrier(true); - - card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - recordNode(*card, [](TestCanvas& canvas) { - SkPaint paint; - paint.setAntiAlias(true); - paint.setColor(0xFF000000); - canvas.drawOval(0, 0, 200, 200, paint); - }); - canvas->drawRenderNode(card.get()); - - canvas->insertReorderBarrier(false); - } - - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -}; -static Benchmark _Oval(BenchmarkInfo{ - "oval", - "Draws 1 oval.", - TreeContentAnimation::run<OvalAnimation> -}); - -class PartialDamageTest : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, TestCanvas* canvas) override { - static SkColor COLORS[] = { - 0xFFF44336, - 0xFF9C27B0, - 0xFF2196F3, - 0xFF4CAF50, - }; - - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - - for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { - for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { - sp<RenderNode> card = createCard(x, y, dp(100), dp(100), - COLORS[static_cast<int>((y / dp(116))) % 4]); - canvas->drawRenderNode(card.get()); - cards.push_back(card); - } - } - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - cards[0]->mutateStagingProperties().setTranslationX(curFrame); - cards[0]->mutateStagingProperties().setTranslationY(curFrame); - cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - recordNode(*cards[0], [curFrame](TestCanvas& canvas) { - canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), - SkXfermode::kSrcOver_Mode); - }); - } - - static SkColor interpolateColor(float fraction, SkColor start, SkColor end) { - int startA = (start >> 24) & 0xff; - int startR = (start >> 16) & 0xff; - int startG = (start >> 8) & 0xff; - int startB = start & 0xff; - - int endA = (end >> 24) & 0xff; - int endR = (end >> 16) & 0xff; - int endG = (end >> 8) & 0xff; - int endB = end & 0xff; - - return (int)((startA + (int)(fraction * (endA - startA))) << 24) | - (int)((startR + (int)(fraction * (endR - startR))) << 16) | - (int)((startG + (int)(fraction * (endG - startG))) << 8) | - (int)((startB + (int)(fraction * (endB - startB)))); - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - recordNode(*node, [color](TestCanvas& canvas) { - canvas.drawColor(color, SkXfermode::kSrcOver_Mode); - }); - return node; - } -}; -static Benchmark _PartialDamage(BenchmarkInfo{ - "partialdamage", - "Tests the partial invalidation path. Draws a grid of rects and animates 1 " - "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or " - "EGL_KHR_partial_update is supported by the device & are enabled in hwui.", - TreeContentAnimation::run<PartialDamageTest> -}); - - -class SaveLayerAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card = new RenderNode(); - void createContent(int width, int height, TestCanvas* canvas) override { - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background - - card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - recordNode(*card, [](TestCanvas& canvas) { - canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag); - canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped - canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag); - canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped - canvas.restore(); - canvas.restore(); - }); - - canvas->drawRenderNode(card.get()); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -}; -static Benchmark _SaveLayer(BenchmarkInfo{ - "savelayer", - "A nested pair of clipped saveLayer operations. " - "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.", - TreeContentAnimation::run<SaveLayerAnimation> -}); - - -class HwLayerAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) { - canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); - }, TestUtils::getHwLayerSetupCallback()); - void createContent(int width, int height, TestCanvas* canvas) override { - canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background - canvas->drawRenderNode(card.get()); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -}; -static Benchmark _HwLayer(BenchmarkInfo{ - "hwlayer", - "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. " - "Tests the hardware layer codepath.", - TreeContentAnimation::run<HwLayerAnimation> -}); diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp index aee84de3ae7b..48566e8f6ecd 100644 --- a/libs/hwui/tests/main.cpp +++ b/libs/hwui/tests/main.cpp @@ -43,6 +43,8 @@ static int gFrameCount = 150; static int gRepeatCount = 1; static std::vector<BenchmarkInfo> gRunTests; +void run(const BenchmarkInfo& info, const BenchmarkOptions& opts); + static void printHelp() { printf("\ USAGE: hwuitest [OPTIONS] <TESTNAME>\n\ @@ -186,7 +188,7 @@ int main(int argc, char* argv[]) { opts.count = gFrameCount; for (int i = 0; i < gRepeatCount; i++) { for (auto&& test : gRunTests) { - test.functor(opts); + run(test, opts); } } printf("Success!\n"); diff --git a/libs/hwui/tests/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/scenes/HwLayerAnimation.cpp new file mode 100644 index 000000000000..e316eca79be8 --- /dev/null +++ b/libs/hwui/tests/scenes/HwLayerAnimation.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class HwLayerAnimation; + +static Benchmark _HwLayer(BenchmarkInfo{ + "hwlayer", + "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. " + "Tests the hardware layer codepath.", + simpleCreateScene<HwLayerAnimation> +}); + +class HwLayerAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + card = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, TestCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); + }); + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background + canvas.drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp new file mode 100644 index 000000000000..919a53d118b3 --- /dev/null +++ b/libs/hwui/tests/scenes/OvalAnimation.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class OvalAnimation; + +static Benchmark _Oval(BenchmarkInfo{ + "oval", + "Draws 1 oval.", + simpleCreateScene<OvalAnimation> +}); + +class OvalAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(0xFF000000); + canvas.drawOval(0, 0, 200, 200, paint); + }); + + canvas.drawRenderNode(card.get()); + canvas.insertReorderBarrier(false); + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp new file mode 100644 index 000000000000..0fba4eb5f9ff --- /dev/null +++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class PartialDamageAnimation; + +static Benchmark _PartialDamage(BenchmarkInfo{ + "partialdamage", + "Tests the partial invalidation path. Draws a grid of rects and animates 1 " + "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or " + "EGL_KHR_partial_update is supported by the device & are enabled in hwui.", + simpleCreateScene<PartialDamageAnimation> +}); + +class PartialDamageAnimation : public TestScene { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + static SkColor COLORS[] = { + 0xFFF44336, + 0xFF9C27B0, + 0xFF2196F3, + 0xFF4CAF50, + }; + + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4]; + sp<RenderNode> card = TestUtils::createNode(x, y, + x + dp(100), y + dp(100), + [color](TestCanvas& canvas) { + canvas.drawColor(color, SkXfermode::kSrcOver_Mode); + }); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + } + } + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + cards[0]->mutateStagingProperties().setTranslationX(curFrame); + cards[0]->mutateStagingProperties().setTranslationY(curFrame); + cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) { + SkColor color = TestUtils::interpolateColor( + curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0); + canvas.drawColor(color, SkXfermode::kSrcOver_Mode); + }); + } +}; diff --git a/libs/hwui/tests/scenes/RecentsAnimation.cpp b/libs/hwui/tests/scenes/RecentsAnimation.cpp new file mode 100644 index 000000000000..1e38d84dac1f --- /dev/null +++ b/libs/hwui/tests/scenes/RecentsAnimation.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class RecentsAnimation; + +static Benchmark _Recents(BenchmarkInfo{ + "recents", + "A recents-like scrolling list of textures. " + "Consists of updating a texture every frame", + simpleCreateScene<RecentsAnimation> +}); + +class RecentsAnimation : public TestScene { +public: + void createContent(int width, int height, TestCanvas& renderer) override { + static SkColor COLORS[] = { + 0xFFF44336, + 0xFF9C27B0, + 0xFF2196F3, + 0xFF4CAF50, + }; + + thumbnailSize = std::min(std::min(width, height) / 2, 720); + int cardsize = std::min(width, height) - dp(64); + + renderer.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + renderer.insertReorderBarrier(true); + + int x = dp(32); + for (int i = 0; i < 4; i++) { + int y = (height / 4) * i; + SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize); + thumb.eraseColor(COLORS[i]); + sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb); + card->mutateStagingProperties().setElevation(i * dp(8)); + renderer.drawRenderNode(card.get()); + mThumbnail = thumb; + mCards.push_back(card); + } + + renderer.insertReorderBarrier(false); + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < mCards.size(); ci++) { + mCards[ci]->mutateStagingProperties().setTranslationY(curFrame); + mCards[ci]->setPropertyFieldsDirty(RenderNode::Y); + } + mThumbnail.eraseColor(TestUtils::interpolateColor( + curFrame / 150.0f, 0xFF4CAF50, 0xFFFF5722)); + } + +private: + sp<RenderNode> createCard(int x, int y, int width, int height, + const SkBitmap& thumb) { + return TestUtils::createNode(x, y, x + width, y + height, + [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) { + props.setElevation(dp(16)); + props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1); + props.mutableOutline().setShouldClip(true); + + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(), + 0, 0, width, height, nullptr); + }); + } + + SkBitmap mThumbnail; + std::vector< sp<RenderNode> > mCards; + int thumbnailSize; +}; diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp new file mode 100644 index 000000000000..254f8280cb66 --- /dev/null +++ b/libs/hwui/tests/scenes/RectGridAnimation.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include "TestSceneBase.h" + +class RectGridAnimation; + +static Benchmark _RectGrid(BenchmarkInfo{ + "rectgrid", + "A dense grid of 1x1 rects that should visually look like a single rect. " + "Low CPU/GPU load.", + simpleCreateScene<RectGridAnimation> +}); + +class RectGridAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + card = TestUtils::createNode(50, 50, 250, 250, + [](TestCanvas& canvas) { + canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); + + SkRegion region; + for (int xOffset = 0; xOffset < 200; xOffset+=2) { + for (int yOffset = 0; yOffset < 200; yOffset+=2) { + region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); + } + } + + SkPaint paint; + paint.setColor(0xff00ffff); + canvas.drawRegion(region, paint); + }); + canvas.drawRenderNode(card.get()); + + canvas.insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp new file mode 100644 index 000000000000..c62dd19968c3 --- /dev/null +++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class SaveLayerAnimation; + +static Benchmark _SaveLayer(BenchmarkInfo{ + "savelayer", + "A nested pair of clipped saveLayer operations. " + "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.", + simpleCreateScene<SaveLayerAnimation> +}); + +class SaveLayerAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background + + card = TestUtils::createNode(0, 0, 200, 200, + [](TestCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped + canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped + canvas.restore(); + canvas.restore(); + }); + + canvas.drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp new file mode 100644 index 000000000000..26c86aa6f9d5 --- /dev/null +++ b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class ShadowGrid2Animation; + +static Benchmark _ShadowGrid2(BenchmarkInfo{ + "shadowgrid2", + "A dense grid of rounded rects that cast a shadow. This is a higher CPU load " + "variant of shadowgrid. Very high CPU load, high GPU load.", + simpleCreateScene<ShadowGrid2Animation> +}); + +class ShadowGrid2Animation : public TestScene { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { + for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { + sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + } + } + + canvas.insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + return TestUtils::createNode(x, y, x + width, y + height, + [width, height](RenderProperties& props, TestCanvas& canvas) { + props.setElevation(dp(16)); + props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); + props.mutableOutline().setShouldClip(true); + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); + } +}; diff --git a/libs/hwui/tests/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp new file mode 100644 index 000000000000..ee3c590c4e0d --- /dev/null +++ b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSceneBase.h" + +class ShadowGridAnimation; + +static Benchmark _ShadowGrid(BenchmarkInfo{ + "shadowgrid", + "A grid of rounded rects that cast a shadow. Simplified scenario of an " + "Android TV-style launcher interface. High CPU/GPU load.", + simpleCreateScene<ShadowGridAnimation> +}); + +class ShadowGridAnimation : public TestScene { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + } + } + + canvas.insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + return TestUtils::createNode(x, y, x + width, y + height, + [width, height](RenderProperties& props, TestCanvas& canvas) { + props.setElevation(dp(16)); + props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); + props.mutableOutline().setShouldClip(true); + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); + } +}; diff --git a/libs/hwui/tests/scenes/TestSceneBase.h b/libs/hwui/tests/scenes/TestSceneBase.h new file mode 100644 index 000000000000..a208509edf0e --- /dev/null +++ b/libs/hwui/tests/scenes/TestSceneBase.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TESTS_SCENES_TESTSCENEBASE_H +#define TESTS_SCENES_TESTSCENEBASE_H + +#include "DisplayListCanvas.h" +#include "RecordingCanvas.h" +#include "RenderNode.h" +#include "tests/Benchmark.h" +#include "tests/TestContext.h" +#include "tests/TestScene.h" +#include "utils/TestUtils.h" + +#include <functional> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */ diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp index 4e00fb3a2f86..7ad2f9bdbb67 100644 --- a/libs/hwui/unit_tests/BakedOpStateTests.cpp +++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp @@ -18,7 +18,7 @@ #include <BakedOpState.h> #include <RecordedOp.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp index 3ef329a0d51b..c6ccf4dd9bea 100644 --- a/libs/hwui/unit_tests/FatVectorTests.cpp +++ b/libs/hwui/unit_tests/FatVectorTests.cpp @@ -17,7 +17,7 @@ #include <gtest/gtest.h> #include <utils/FatVector.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp index ef205ec50ffc..05fd08a3340a 100644 --- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp +++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp @@ -19,7 +19,7 @@ #include <LayerUpdateQueue.h> #include <RenderNode.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp index 0f6b2494c237..0591db628bd1 100644 --- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp +++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp @@ -17,7 +17,7 @@ #include <gtest/gtest.h> #include <utils/LinearAllocator.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp index ba921572fd09..de86aedfa26a 100644 --- a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp +++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp @@ -17,7 +17,7 @@ #include <gtest/gtest.h> #include <renderstate/OffscreenBufferPool.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> using namespace android; using namespace android::uirenderer; diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index 07a1855490d4..ec8048dae014 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -21,7 +21,7 @@ #include <OpReorderer.h> #include <RecordedOp.h> #include <RecordingCanvas.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> #include <unordered_map> @@ -186,14 +186,14 @@ TEST(OpReorderer, renderNode) { } }; - sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) { + sp<RenderNode> child = TestUtils::createNode(10, 10, 110, 110, [](RecordingCanvas& canvas) { SkPaint paint; paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); }); RenderNode* childPtr = child.get(); - sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) { + sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) { SkPaint paint; paint.setColor(SK_ColorDKGRAY); canvas.drawRect(0, 0, 200, 200, paint); @@ -221,7 +221,7 @@ TEST(OpReorderer, clipped) { } }; - sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) { + sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) { SkBitmap bitmap = TestUtils::createSkBitmap(200, 200); canvas.drawBitmap(bitmap, 0, 0, nullptr); }); @@ -396,11 +396,13 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) { } }; - sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) { + sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); SkPaint paint; paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); - }, TestUtils::getHwLayerSetupCallback()); + }); OffscreenBuffer** layerHandle = node->getLayerHandle(); // create RenderNode's layer here in same way prepareTree would @@ -483,18 +485,20 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) { } }; - auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, - [](RecordingCanvas& canvas) { + auto child = TestUtils::createNode(50, 50, 150, 150, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); SkPaint paint; paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); - }, TestUtils::getHwLayerSetupCallback()); + }); OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100); *(child->getLayerHandle()) = &childLayer; RenderNode* childPtr = child.get(); - auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, - [childPtr](RecordingCanvas& canvas) { + auto parent = TestUtils::createNode(0, 0, 200, 200, + [childPtr](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); SkPaint paint; paint.setColor(SK_ColorDKGRAY); canvas.drawRect(0, 0, 200, 200, paint); @@ -502,7 +506,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) { canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag); canvas.drawRenderNode(childPtr); canvas.restore(); - }, TestUtils::getHwLayerSetupCallback()); + }); OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200); *(parent->getLayerHandle()) = &parentLayer; @@ -529,7 +533,7 @@ static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) canvas->drawRect(0, 0, 100, 100, paint); } static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) { - auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, + auto node = TestUtils::createNode(0, 0, 100, 100, [expectedDrawOrder](RecordingCanvas& canvas) { drawOrderedRect(&canvas, expectedDrawOrder); }); @@ -546,7 +550,7 @@ TEST(OpReorderer, zReorder) { } }; - auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, + auto parent = TestUtils::createNode(0, 0, 100, 100, [](RecordingCanvas& canvas) { drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder drawOrderedRect(&canvas, 1); @@ -570,15 +574,13 @@ TEST(OpReorderer, zReorder) { // creates a 100x100 shadow casting node with provided translationZ static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) { - return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, - [](RecordingCanvas& canvas) { + return TestUtils::createNode(0, 0, 100, 100, + [translationZ] (RenderProperties& properties, RecordingCanvas& canvas) { + properties.setTranslationZ(translationZ); + properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f); SkPaint paint; paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); - }, [translationZ] (RenderProperties& properties) { - properties.setTranslationZ(translationZ); - properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f); - return RenderNode::GENERIC | RenderNode::TRANSLATION_Z; }); } @@ -600,7 +602,7 @@ TEST(OpReorderer, shadow) { } }; - sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, + sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [] (RecordingCanvas& canvas) { canvas.insertReorderBarrier(true); canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); @@ -636,7 +638,7 @@ TEST(OpReorderer, shadowSaveLayer) { } }; - sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, + sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [] (RecordingCanvas& canvas) { // save/restore outside of reorderBarrier, so they don't get moved out of place canvas.translate(20, 10); @@ -676,14 +678,15 @@ RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) { } }; - sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160, - [] (RecordingCanvas& canvas) { + sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); canvas.insertReorderBarrier(true); canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); canvas.translate(20, 10); canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); canvas.restore(); - }, TestUtils::getHwLayerSetupCallback()); + }); OffscreenBuffer** layerHandle = parent->getLayerHandle(); // create RenderNode's layer here in same way prepareTree would, setting windowTransform @@ -718,7 +721,7 @@ TEST(OpReorderer, shadowLayering) { EXPECT_TRUE(index == 2 || index == 3); } }; - sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, + sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [] (RecordingCanvas& canvas) { canvas.insertReorderBarrier(true); canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); @@ -732,7 +735,7 @@ TEST(OpReorderer, shadowLayering) { EXPECT_EQ(4, renderer.getIndex()); } -static void testProperty(TestUtils::PropSetupCallback propSetupCallback, +static void testProperty(std::function<void(RenderProperties&)> propSetupCallback, std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) { class PropertyTestRenderer : public TestRendererBase { public: @@ -745,11 +748,13 @@ static void testProperty(TestUtils::PropSetupCallback propSetupCallback, std::function<void(const RectOp&, const BakedOpState&)> mCallback; }; - auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) { + auto node = TestUtils::createNode(0, 0, 100, 100, + [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) { + propSetupCallback(props); SkPaint paint; paint.setColor(SK_ColorWHITE); canvas.drawRect(0, 0, 100, 100, paint); - }, propSetupCallback); + }); OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, createSyncedNodeList(node), sLightCenter); @@ -762,7 +767,6 @@ TEST(OpReorderer, renderPropOverlappingRenderingAlpha) { testProperty([](RenderProperties& properties) { properties.setAlpha(0.5f); properties.setHasOverlappingRendering(false); - return RenderNode::ALPHA | RenderNode::GENERIC; }, [](const RectOp& op, const BakedOpState& state) { EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op"; }); @@ -772,7 +776,6 @@ TEST(OpReorderer, renderPropClipping) { testProperty([](RenderProperties& properties) { properties.setClipToBounds(true); properties.setClipBounds(Rect(10, 20, 300, 400)); - return RenderNode::GENERIC; }, [](const RectOp& op, const BakedOpState& state) { EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds) << "Clip rect should be intersection of node bounds and clip bounds"; @@ -782,7 +785,6 @@ TEST(OpReorderer, renderPropClipping) { TEST(OpReorderer, renderPropRevealClip) { testProperty([](RenderProperties& properties) { properties.mutableRevealClip().set(true, 50, 50, 25); - return RenderNode::GENERIC; }, [](const RectOp& op, const BakedOpState& state) { ASSERT_NE(nullptr, state.roundRectClipState); EXPECT_TRUE(state.roundRectClipState->highPriority); @@ -795,7 +797,6 @@ TEST(OpReorderer, renderPropOutlineClip) { testProperty([](RenderProperties& properties) { properties.mutableOutline().setShouldClip(true); properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f); - return RenderNode::GENERIC; }, [](const RectOp& op, const BakedOpState& state) { ASSERT_NE(nullptr, state.roundRectClipState); EXPECT_FALSE(state.roundRectClipState->highPriority); @@ -819,9 +820,6 @@ TEST(OpReorderer, renderPropTransform) { properties.setTranslationY(20); properties.setScaleX(0.5f); properties.setScaleY(0.7f); - return RenderNode::GENERIC - | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y - | RenderNode::SCALE_X | RenderNode::SCALE_Y; }, [](const RectOp& op, const BakedOpState& state) { Matrix4 matrix; matrix.loadTranslate(10, 10, 0); // left, top @@ -857,7 +855,7 @@ struct SaveLayerAlphaData { * (for efficiency, and to fit in layer size constraints) based on parent clip. */ void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, - TestUtils::PropSetupCallback propSetupCallback) { + std::function<void(RenderProperties&)> propSetupCallback) { class SaveLayerAlphaClipTestRenderer : public TestRendererBase { public: SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData) @@ -887,17 +885,16 @@ void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize()) << "Node must be bigger than max texture size to exercise saveLayer codepath"; - auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 10000, 10000, [](RecordingCanvas& canvas) { - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 10000, 10000, paint); - }, [&propSetupCallback](RenderProperties& properties) { + auto node = TestUtils::createNode(0, 0, 10000, 10000, + [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) { properties.setHasOverlappingRendering(true); properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer - // apply other properties - int flags = propSetupCallback(properties); - return RenderNode::GENERIC | RenderNode::ALPHA | flags; + propSetupCallback(properties); + + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 10000, 10000, paint); }); auto nodes = createSyncedNodeList(node); // sync before querying height @@ -914,7 +911,6 @@ TEST(OpReorderer, renderPropSaveLayerAlphaClipBig) { testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { properties.setTranslationX(10); // offset rendering content properties.setTranslationY(-2000); // offset rendering content - return RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y; }); EXPECT_EQ(190u, observedData.layerWidth); EXPECT_EQ(200u, observedData.layerHeight); @@ -937,9 +933,6 @@ TEST(OpReorderer, renderPropSaveLayerAlphaRotate) { properties.setPivotX(0); properties.setPivotY(0); properties.setRotation(45); - return RenderNode::GENERIC - | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y - | RenderNode::ROTATION; }); // ceil(sqrt(2) / 2 * 200) = 142 EXPECT_EQ(142u, observedData.layerWidth); @@ -955,7 +948,6 @@ TEST(OpReorderer, renderPropSaveLayerAlphaScale) { properties.setPivotY(0); properties.setScaleX(2); properties.setScaleY(0.5f); - return RenderNode::GENERIC | RenderNode::SCALE_X | RenderNode::SCALE_Y; }); EXPECT_EQ(100u, observedData.layerWidth); EXPECT_EQ(400u, observedData.layerHeight); diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp index 83b37ab733a2..22190f50cbe5 100644 --- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp +++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp @@ -18,7 +18,7 @@ #include <RecordedOp.h> #include <RecordingCanvas.h> -#include <unit_tests/TestUtils.h> +#include <utils/TestUtils.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/VectorDrawableTests.cpp index c99d7b0a0368..77dd73acff10 100644 --- a/libs/hwui/unit_tests/PathParserTests.cpp +++ b/libs/hwui/unit_tests/VectorDrawableTests.cpp @@ -17,7 +17,8 @@ #include <gtest/gtest.h> #include "PathParser.h" -#include "VectorDrawablePath.h" +#include "utils/MathUtils.h" +#include "utils/VectorDrawableUtils.h" #include <functional> @@ -177,6 +178,10 @@ const StringPath sStringPaths[] = { {"1-2e34567", false} }; +static bool hasSameVerbs(const PathData& from, const PathData& to) { + return from.verbs == to.verbs && from.verbSizes == to.verbSizes; +} + TEST(PathParser, parseStringForData) { for (TestData testData: sTestDataSet) { PathParser::ParseResult result; @@ -197,12 +202,12 @@ TEST(PathParser, parseStringForData) { } } -TEST(PathParser, createSkPathFromPathData) { +TEST(VectorDrawableUtils, createSkPathFromPathData) { for (TestData testData: sTestDataSet) { SkPath expectedPath; testData.skPathLamda(&expectedPath); SkPath actualPath; - VectorDrawablePath::verbsToPath(&actualPath, &testData.pathData); + VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData); EXPECT_EQ(expectedPath, actualPath); } } @@ -230,5 +235,55 @@ TEST(PathParser, parseStringForSkPath) { } } +TEST(VectorDrawableUtils, morphPathData) { + for (TestData fromData: sTestDataSet) { + for (TestData toData: sTestDataSet) { + bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData); + if (fromData.pathData == toData.pathData) { + EXPECT_TRUE(canMorph); + } else { + bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData); + EXPECT_EQ(expectedToMorph, canMorph); + } + } + } +} + +TEST(VectorDrawableUtils, interpolatePathData) { + // Interpolate path data with itself and every other path data + for (TestData fromData: sTestDataSet) { + for (TestData toData: sTestDataSet) { + PathData outData; + bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData, + toData.pathData, 0.5); + bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData); + EXPECT_EQ(expectedToMorph, success); + } + } + + float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1}; + // Now try to interpolate with a slightly modified version of self and expect success + for (TestData fromData : sTestDataSet) { + PathData toPathData = fromData.pathData; + for (size_t i = 0; i < toPathData.points.size(); i++) { + toPathData.points[i]++; + } + const PathData& fromPathData = fromData.pathData; + PathData outData; + // Interpolate the two path data with different fractions + for (float fraction : fractions) { + bool success = VectorDrawableUtils::interpolatePathData( + &outData, fromPathData, toPathData, fraction); + EXPECT_TRUE(success); + for (size_t i = 0; i < outData.points.size(); i++) { + float expectedResult = fromPathData.points[i] * (1.0 - fraction) + + toPathData.points[i] * fraction; + EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i])); + } + } + } +} + + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp new file mode 100644 index 000000000000..84230a72f1c2 --- /dev/null +++ b/libs/hwui/utils/TestUtils.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestUtils.h" + +namespace android { +namespace uirenderer { + +SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) { + int startA = (start >> 24) & 0xff; + int startR = (start >> 16) & 0xff; + int startG = (start >> 8) & 0xff; + int startB = start & 0xff; + + int endA = (end >> 24) & 0xff; + int endR = (end >> 16) & 0xff; + int endG = (end >> 8) & 0xff; + int endB = end & 0xff; + + return (int)((startA + (int)(fraction * (endA - startA))) << 24) + | (int)((startR + (int)(fraction * (endR - startR))) << 16) + | (int)((startG + (int)(fraction * (endG - startG))) << 8) + | (int)((startB + (int)(fraction * (endB - startB)))); +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/utils/TestUtils.h index 38bafd52cac8..f7f4f2d1ca10 100644 --- a/libs/hwui/unit_tests/TestUtils.h +++ b/libs/hwui/utils/TestUtils.h @@ -27,8 +27,10 @@ #if HWUI_NEW_OPS #include <RecordedOp.h> +#include <RecordingCanvas.h> #else #include <DisplayListOp.h> +#include <DisplayListCanvas.h> #endif #include <memory> @@ -36,6 +38,12 @@ namespace android { namespace uirenderer { +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + #define EXPECT_MATRIX_APPROX_EQ(a, b) \ EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b)) @@ -97,7 +105,8 @@ public: static SkBitmap createSkBitmap(int width, int height) { SkBitmap bitmap; - SkImageInfo info = SkImageInfo::MakeUnknown(width, height); + SkImageInfo info = SkImageInfo::Make(width, height, + kN32_SkColorType, kPremul_SkAlphaType); bitmap.setInfo(info); bitmap.allocPixels(info); return bitmap; @@ -111,18 +120,8 @@ public: return std::unique_ptr<DisplayList>(canvas.finishRecording()); } - typedef std::function<int(RenderProperties&)> PropSetupCallback; - - static PropSetupCallback getHwLayerSetupCallback() { - static PropSetupCallback sLayerSetupCallback = [] (RenderProperties& properties) { - properties.mutateLayerProperties().setType(LayerType::RenderLayer); - return RenderNode::GENERIC; - }; - return sLayerSetupCallback; - } - static sp<RenderNode> createNode(int left, int top, int right, int bottom, - PropSetupCallback propSetupCallback = nullptr) { + std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) { #if HWUI_NULL_GPU // if RenderNodes are being sync'd/used, device info will be needed, since // DeviceInfo::maxTextureSize() affects layer property @@ -130,25 +129,39 @@ public: #endif sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - if (propSetupCallback) { - node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties())); + RenderProperties& props = node->mutateStagingProperties(); + props.setLeftTopRightBottom(left, top, right, bottom); + if (setup) { + TestCanvas canvas(props.getWidth(), props.getHeight()); + setup(props, canvas); + node->setStagingDisplayList(canvas.finishRecording()); } + node->setPropertyFieldsDirty(0xFFFFFFFF); return node; } - template<class CanvasType> static sp<RenderNode> createNode(int left, int top, int right, int bottom, - std::function<void(CanvasType& canvas)> canvasCallback, - PropSetupCallback propSetupCallback = nullptr) { - sp<RenderNode> node = createNode(left, top, right, bottom, propSetupCallback); + std::function<void(RenderProperties& props)> setup) { + return createNode(left, top, right, bottom, + [&setup](RenderProperties& props, TestCanvas& canvas) { + setup(props); + }); + } - auto&& props = node->stagingProperties(); // staging, since not sync'd yet - CanvasType canvas(props.getWidth(), props.getHeight()); - canvasCallback(canvas); - node->setStagingDisplayList(canvas.finishRecording()); - return node; + static sp<RenderNode> createNode(int left, int top, int right, int bottom, + std::function<void(TestCanvas& canvas)> setup) { + return createNode(left, top, right, bottom, + [&setup](RenderProperties& props, TestCanvas& canvas) { + setup(canvas); + }); + } + + static void recordNode(RenderNode& node, + std::function<void(TestCanvas&)> contentCallback) { + TestCanvas canvas(node.stagingProperties().getWidth(), + node.stagingProperties().getHeight()); + contentCallback(canvas); + node.setStagingDisplayList(canvas.finishRecording()); } static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) { @@ -180,6 +193,9 @@ public: TestTask task(rtCallback); renderthread::RenderThread::getInstance().queueAndWait(&task); } + + static SkColor interpolateColor(float fraction, SkColor start, SkColor end); + private: static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { node->syncProperties(); diff --git a/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp new file mode 100644 index 000000000000..ca75c5945b7f --- /dev/null +++ b/libs/hwui/utils/VectorDrawableUtils.cpp @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "VectorDrawableUtils.h" + +#include "PathParser.h" + +#include <math.h> +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +class PathResolver { +public: + float currentX = 0; + float currentY = 0; + float ctrlPointX = 0; + float ctrlPointY = 0; + float currentSegmentStartX = 0; + float currentSegmentStartY = 0; + void addCommand(SkPath* outPath, char previousCmd, + char cmd, const std::vector<float>* points, size_t start, size_t end); +}; + +bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) { + if (morphFrom.verbs.size() != morphTo.verbs.size()) { + return false; + } + + for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) { + if (morphFrom.verbs[i] != morphTo.verbs[i] + || morphFrom.verbSizes[i] != morphTo.verbSizes[i]) { + return false; + } + } + return true; +} + +bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom, + const PathData& morphTo, float fraction) { + if (!canMorph(morphFrom, morphTo)) { + return false; + } + interpolatePaths(outData, morphFrom, morphTo, fraction); + return true; +} + + /** + * Convert an array of PathVerb to Path. + */ +void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) { + PathResolver resolver; + char previousCommand = 'm'; + size_t start = 0; + outPath->reset(); + for (unsigned int i = 0; i < data.verbs.size(); i++) { + size_t verbSize = data.verbSizes[i]; + resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start, + start + verbSize); + previousCommand = data.verbs[i]; + start += verbSize; + } +} + +/** + * The current PathVerb will be interpolated between the + * <code>nodeFrom</code> and <code>nodeTo</code> according to the + * <code>fraction</code>. + * + * @param nodeFrom The start value as a PathVerb. + * @param nodeTo The end value as a PathVerb + * @param fraction The fraction to interpolate. + */ +void VectorDrawableUtils::interpolatePaths(PathData* outData, + const PathData& from, const PathData& to, float fraction) { + outData->points.resize(from.points.size()); + outData->verbSizes = from.verbSizes; + outData->verbs = from.verbs; + + for (size_t i = 0; i < from.points.size(); i++) { + outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction; + } +} + +/** + * Converts an arc to cubic Bezier segments and records them in p. + * + * @param p The target for the cubic Bezier segments + * @param cx The x coordinate center of the ellipse + * @param cy The y coordinate center of the ellipse + * @param a The radius of the ellipse in the horizontal direction + * @param b The radius of the ellipse in the vertical direction + * @param e1x E(eta1) x coordinate of the starting point of the arc + * @param e1y E(eta2) y coordinate of the starting point of the arc + * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane + * @param start The start angle of the arc on the ellipse + * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse + */ +static void arcToBezier(SkPath* p, + double cx, + double cy, + double a, + double b, + double e1x, + double e1y, + double theta, + double start, + double sweep) { + // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html + // and http://www.spaceroots.org/documents/ellipse/node22.html + + // Maximum of 45 degrees per cubic Bezier segment + int numSegments = ceil(fabs(sweep * 4 / M_PI)); + + double eta1 = start; + double cosTheta = cos(theta); + double sinTheta = sin(theta); + double cosEta1 = cos(eta1); + double sinEta1 = sin(eta1); + double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); + double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); + + double anglePerSegment = sweep / numSegments; + for (int i = 0; i < numSegments; i++) { + double eta2 = eta1 + anglePerSegment; + double sinEta2 = sin(eta2); + double cosEta2 = cos(eta2); + double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); + double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); + double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; + double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; + double tanDiff2 = tan((eta2 - eta1) / 2); + double alpha = + sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; + double q1x = e1x + alpha * ep1x; + double q1y = e1y + alpha * ep1y; + double q2x = e2x - alpha * ep2x; + double q2y = e2y - alpha * ep2y; + + p->cubicTo((float) q1x, + (float) q1y, + (float) q2x, + (float) q2y, + (float) e2x, + (float) e2y); + eta1 = eta2; + e1x = e2x; + e1y = e2y; + ep1x = ep2x; + ep1y = ep2y; + } +} + +inline double toRadians(float theta) { return theta * M_PI / 180;} + +static void drawArc(SkPath* p, + float x0, + float y0, + float x1, + float y1, + float a, + float b, + float theta, + bool isMoreThanHalf, + bool isPositiveArc) { + + /* Convert rotation angle from degrees to radians */ + double thetaD = toRadians(theta); + /* Pre-compute rotation matrix entries */ + double cosTheta = cos(thetaD); + double sinTheta = sin(thetaD); + /* Transform (x0, y0) and (x1, y1) into unit space */ + /* using (inverse) rotation, followed by (inverse) scale */ + double x0p = (x0 * cosTheta + y0 * sinTheta) / a; + double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; + double x1p = (x1 * cosTheta + y1 * sinTheta) / a; + double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; + + /* Compute differences and averages */ + double dx = x0p - x1p; + double dy = y0p - y1p; + double xm = (x0p + x1p) / 2; + double ym = (y0p + y1p) / 2; + /* Solve for intersecting unit circles */ + double dsq = dx * dx + dy * dy; + if (dsq == 0.0) { + ALOGW("Points are coincident"); + return; /* Points are coincident */ + } + double disc = 1.0 / dsq - 1.0 / 4.0; + if (disc < 0.0) { + ALOGW("Points are too far apart %f", dsq); + float adjust = (float) (sqrt(dsq) / 1.99999); + drawArc(p, x0, y0, x1, y1, a * adjust, + b * adjust, theta, isMoreThanHalf, isPositiveArc); + return; /* Points are too far apart */ + } + double s = sqrt(disc); + double sdx = s * dx; + double sdy = s * dy; + double cx; + double cy; + if (isMoreThanHalf == isPositiveArc) { + cx = xm - sdy; + cy = ym + sdx; + } else { + cx = xm + sdy; + cy = ym - sdx; + } + + double eta0 = atan2((y0p - cy), (x0p - cx)); + + double eta1 = atan2((y1p - cy), (x1p - cx)); + + double sweep = (eta1 - eta0); + if (isPositiveArc != (sweep >= 0)) { + if (sweep > 0) { + sweep -= 2 * M_PI; + } else { + sweep += 2 * M_PI; + } + } + + cx *= a; + cy *= b; + double tcx = cx; + cx = cx * cosTheta - cy * sinTheta; + cy = tcx * sinTheta + cy * cosTheta; + + arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); +} + + + +// Use the given verb, and points in the range [start, end) to insert a command into the SkPath. +void PathResolver::addCommand(SkPath* outPath, char previousCmd, + char cmd, const std::vector<float>* points, size_t start, size_t end) { + + int incr = 2; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + outPath->close(); + // Path is closed here, but we need to move the pen to the + // closed position. So we cache the segment's starting position, + // and restore it here. + currentX = currentSegmentStartX; + currentY = currentSegmentStartY; + ctrlPointX = currentSegmentStartX; + ctrlPointY = currentSegmentStartY; + outPath->moveTo(currentX, currentY); + break; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + + for (unsigned int k = start; k < end; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + currentX += points->at(k + 0); + currentY += points->at(k + 1); + if (k > start) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + outPath->rLineTo(points->at(k + 0), points->at(k + 1)); + } else { + outPath->rMoveTo(points->at(k + 0), points->at(k + 1)); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'M': // moveto - Start a new sub-path + currentX = points->at(k + 0); + currentY = points->at(k + 1); + if (k > start) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + outPath->lineTo(points->at(k + 0), points->at(k + 1)); + } else { + outPath->moveTo(points->at(k + 0), points->at(k + 1)); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'l': // lineto - Draw a line from the current point (relative) + outPath->rLineTo(points->at(k + 0), points->at(k + 1)); + currentX += points->at(k + 0); + currentY += points->at(k + 1); + break; + case 'L': // lineto - Draw a line from the current point + outPath->lineTo(points->at(k + 0), points->at(k + 1)); + currentX = points->at(k + 0); + currentY = points->at(k + 1); + break; + case 'h': // horizontal lineto - Draws a horizontal line (relative) + outPath->rLineTo(points->at(k + 0), 0); + currentX += points->at(k + 0); + break; + case 'H': // horizontal lineto - Draws a horizontal line + outPath->lineTo(points->at(k + 0), currentY); + currentX = points->at(k + 0); + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + outPath->rLineTo(0, points->at(k + 0)); + currentY += points->at(k + 0); + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + outPath->lineTo(currentX, points->at(k + 0)); + currentY = points->at(k + 0); + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), + points->at(k + 4), points->at(k + 5)); + + ctrlPointX = currentX + points->at(k + 2); + ctrlPointY = currentY + points->at(k + 3); + currentX += points->at(k + 4); + currentY += points->at(k + 5); + + break; + case 'C': // curveto - Draws a cubic Bézier curve + outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), + points->at(k + 4), points->at(k + 5)); + currentX = points->at(k + 4); + currentY = points->at(k + 5); + ctrlPointX = points->at(k + 2); + ctrlPointY = points->at(k + 3); + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1), + points->at(k + 2), points->at(k + 3)); + ctrlPointX = currentX + points->at(k + 0); + ctrlPointY = currentY + points->at(k + 1); + currentX += points->at(k + 2); + currentY += points->at(k + 3); + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = points->at(k + 0); + ctrlPointY = points->at(k + 1); + currentX = points->at(k + 2); + currentY = points->at(k + 3); + break; + case 'q': // Draws a quadratic Bézier (relative) + outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = currentX + points->at(k + 0); + ctrlPointY = currentY + points->at(k + 1); + currentX += points->at(k + 2); + currentY += points->at(k + 3); + break; + case 'Q': // Draws a quadratic Bézier + outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = points->at(k + 0); + ctrlPointY = points->at(k + 1); + currentX = points->at(k + 2); + currentY = points->at(k + 3); + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1)); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += points->at(k + 0); + currentY += points->at(k + 1); + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1)); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = points->at(k + 0); + currentY = points->at(k + 1); + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(outPath, + currentX, + currentY, + points->at(k + 5) + currentX, + points->at(k + 6) + currentY, + points->at(k + 0), + points->at(k + 1), + points->at(k + 2), + points->at(k + 3) != 0, + points->at(k + 4) != 0); + currentX += points->at(k + 5); + currentY += points->at(k + 6); + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(outPath, + currentX, + currentY, + points->at(k + 5), + points->at(k + 6), + points->at(k + 0), + points->at(k + 1), + points->at(k + 2), + points->at(k + 3) != 0, + points->at(k + 4) != 0); + currentX = points->at(k + 5); + currentY = points->at(k + 6); + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + default: + LOG_ALWAYS_FATAL("Unsupported command: %c", cmd); + break; + } + previousCmd = cmd; + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h new file mode 100644 index 000000000000..21c1cdcc37f3 --- /dev/null +++ b/libs/hwui/utils/VectorDrawableUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_VECTORDRAWABLE_UTILS_H +#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H + +#include "VectorDrawablePath.h" + +#include <cutils/compiler.h> +#include "SkPath.h" +#include <vector> + +namespace android { +namespace uirenderer { + +class VectorDrawableUtils { +public: + ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo); + ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom, + const PathData& morphTo, float fraction); + ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data); + static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to, + float fraction); +}; +} // namespace uirenderer +} // namespace android +#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/ diff --git a/packages/DocumentsUI/res/drawable/item_root_background.xml b/packages/DocumentsUI/res/drawable/item_root_background.xml deleted file mode 100644 index c403159cee8c..000000000000 --- a/packages/DocumentsUI/res/drawable/item_root_background.xml +++ /dev/null @@ -1,24 +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_focused="true" android:state_activated="true"> - <color android:color="@color/material_grey_300" /> - </item> - <item android:state_focused="false" android:state_activated="true"> - <color android:color="@color/material_grey_300" /> - </item> -</selector> diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml index 381e1c89b428..fe06eafea713 100644 --- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml @@ -17,7 +17,7 @@ <com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/item_doc_list_background" + android:background="@color/item_doc_background" android:orientation="horizontal" android:focusable="true"> diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml index 0dac0d550eb8..0146f142114f 100644 --- a/packages/DocumentsUI/res/layout/drawer_layout.xml +++ b/packages/DocumentsUI/res/layout/drawer_layout.xml @@ -61,7 +61,7 @@ android:layout_gravity="start" android:orientation="vertical" android:elevation="16dp" - android:background="@*android:color/white"> + android:background="@color/window_background"> <Toolbar android:id="@+id/roots_toolbar" diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml index 403c6675ac30..3135977e702c 100644 --- a/packages/DocumentsUI/res/layout/fixed_layout.xml +++ b/packages/DocumentsUI/res/layout/fixed_layout.xml @@ -50,9 +50,7 @@ android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal" - android:baselineAligned="false" - android:divider="?android:attr/dividerVertical" - android:showDividers="middle"> + android:baselineAligned="false"> <FrameLayout android:id="@+id/container_roots" @@ -62,8 +60,7 @@ <include layout="@layout/directory_cluster" android:layout_width="0dp" android:layout_weight="1" - android:elevation="8dp" - android:background="@color/material_grey_50" /> + android:elevation="8dp" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml index ada7f49dbd1f..f9bbccb57809 100644 --- a/packages/DocumentsUI/res/layout/fragment_directory.xml +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -17,7 +17,6 @@ <com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/material_grey_50" android:orientation="vertical" android:animateLayoutChanges="true"> @@ -78,8 +77,7 @@ android:paddingBottom="0dp" android:clipToPadding="false" android:scrollbarStyle="outsideOverlay" - android:drawSelectorOnTop="true" - android:background="@color/directory_background" /> + android:drawSelectorOnTop="true" /> </FrameLayout> diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml index 1dfb34a9b19b..dcd5cfd16ce5 100644 --- a/packages/DocumentsUI/res/layout/item_doc_grid.xml +++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml @@ -18,101 +18,101 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/grid_item_margin" - android:background="@color/item_doc_grid_background" + android:background="@color/item_doc_background" android:focusable="true"> <!-- Main item thumbnail. Comprised of two overlapping images, the visibility of which is controlled by code in DirectoryFragment.java. --> + <FrameLayout android:id="@+id/thumbnail" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingBottom="8dp"> - + android:layout_height="wrap_content"> + <com.android.documentsui.GridItemThumbnail android:id="@+id/icon_thumb" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="centerCrop" android:contentDescription="@null" /> - - <ImageView + + <com.android.documentsui.GridItemThumbnail android:id="@+id/icon_mime" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="centerInside" android:contentDescription="@null" /> - + </FrameLayout> - + <!-- Item nameplate. Has a mime-type icon and some text fields (title, size, mod-time, etc). --> - <TextView - android:id="@android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/thumbnail" - android:layout_toEndOf="@android:id/icon1" - android:singleLine="true" - android:ellipsize="middle" - android:textAlignment="viewStart" - android:paddingEnd="12dp" - android:textAppearance="@android:style/TextAppearance.Material.Subhead" - android:textColor="@*android:color/primary_text_default_material_light" /> - - <TextView - android:id="@+id/size" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@android:id/title" - android:layout_toEndOf="@android:id/icon1" - android:paddingEnd="4dp" - android:singleLine="true" - android:ellipsize="end" - android:textAlignment="viewStart" - android:textAppearance="@android:style/TextAppearance.Material.Caption" - android:textColor="@*android:color/primary_text_default_material_light" /> - - <TextView - android:id="@+id/date" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@android:id/title" - android:layout_toEndOf="@id/size" - android:paddingEnd="12dp" - android:singleLine="true" - android:ellipsize="end" - android:textAlignment="viewStart" - android:textAppearance="@android:style/TextAppearance.Material.Caption" - android:textColor="@*android:color/primary_text_default_material_light" /> - - <ImageView - android:id="@android:id/icon1" - android:layout_width="wrap_content" + + <RelativeLayout + android:id="@+id/nameplate" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginEnd="8dp" android:layout_below="@id/thumbnail" - android:layout_alignParentLeft="true" - android:layout_alignBottom="@id/size" - android:layout_alignTop="@android:id/title" - android:scaleType="centerInside" - android:contentDescription="@null" - android:paddingStart="12dp" - android:paddingEnd="8dp"/> + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:paddingLeft="12dp" + android:paddingRight="12dp"> - <!-- Use an explicit spacer so we can align things to it. --> - <Space - android:id="@+id/bottomPadding" - android:layout_width="match_parent" - android:layout_height="8dp" - android:layout_below="@id/size" /> + <ImageView + android:id="@android:id/icon1" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginEnd="8dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:scaleType="centerInside" + android:contentDescription="@null"/> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_toEndOf="@android:id/icon1" + android:singleLine="true" + android:ellipsize="middle" + android:textAlignment="viewStart" + android:textAppearance="@android:style/TextAppearance.Material.Subhead" + android:textColor="@*android:color/primary_text_default_material_light" /> + + <TextView + android:id="@+id/size" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toEndOf="@android:id/icon1" + android:layout_below="@android:id/title" + android:layout_marginEnd="4dp" + android:singleLine="true" + android:ellipsize="end" + android:textAlignment="viewStart" + android:textAppearance="@android:style/TextAppearance.Material.Caption" + android:textColor="@*android:color/primary_text_default_material_light" /> + + <TextView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_toEndOf="@id/size" + android:singleLine="true" + android:ellipsize="end" + android:textAlignment="viewStart" + android:textAppearance="@android:style/TextAppearance.Material.Caption" + android:textColor="@*android:color/primary_text_default_material_light" /> + + </RelativeLayout> <!-- An overlay that draws the item border when it is focused. --> <View android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignBottom="@id/bottomPadding" + android:layout_alignBottom="@id/nameplate" android:layout_alignTop="@id/thumbnail" android:layout_alignLeft="@id/thumbnail" android:layout_alignRight="@id/thumbnail" diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index c409166d3625..e0684236846b 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -17,10 +17,10 @@ <com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/item_doc_list_background" + android:background="@color/item_doc_background" android:orientation="horizontal" android:focusable="true"> - + <View android:id="@+id/focus_indicator" android:layout_width="4dp" diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml index 90b1ff6e3c5c..ff80d0730b41 100644 --- a/packages/DocumentsUI/res/layout/item_root.xml +++ b/packages/DocumentsUI/res/layout/item_root.xml @@ -22,8 +22,7 @@ android:paddingEnd="@dimen/list_item_padding" android:gravity="center_vertical" android:orientation="horizontal" - android:baselineAligned="false" - android:background="@drawable/item_root_background"> + android:baselineAligned="false"> <FrameLayout android:layout_width="@dimen/icon_size" diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml index 68c8b65c4f87..153c673caf56 100644 --- a/packages/DocumentsUI/res/values/colors.xml +++ b/packages/DocumentsUI/res/values/colors.xml @@ -17,14 +17,20 @@ <resources> <color name="material_grey_400">#ffbdbdbd</color> + <!-- This is the window background, but also the background for anything + else that needs to manually declare a background matching the "default" + app background (e.g. the drawer overlay). --> + <color name="window_background">#fff1f1f1</color> + <color name="primary_dark">@*android:color/primary_dark_material_dark</color> <color name="primary">@*android:color/material_blue_grey_900</color> <color name="accent">@*android:color/accent_material_light</color> <color name="action_mode">@color/material_grey_400</color> - - <color name="directory_background">@*android:color/material_grey_300</color> - <color name="item_doc_grid_background">@android:color/white</color> - <color name="item_doc_grid_protect_background">@android:color/white</color> + <color name="band_select_background">#88ffffff</color> <color name="band_select_border">#44000000</color> + + <color name="item_doc_background">#fffafafa</color> + <color name="item_doc_background_selected">#ffe0f2f1</color> + </resources> diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml index 15d17cc6b737..6712e2dd4537 100644 --- a/packages/DocumentsUI/res/values/styles.xml +++ b/packages/DocumentsUI/res/values/styles.xml @@ -25,6 +25,7 @@ <item name="actionBarTheme">@style/ActionBarTheme</item> <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item> + <item name="android:windowBackground">@color/window_background</item> <item name="android:colorPrimaryDark">@color/primary_dark</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorAccent">@color/accent</item> @@ -44,6 +45,7 @@ <item name="actionBarTheme">@style/ActionBarTheme</item> <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item> + <item name="android:windowBackground">@color/window_background</item> <item name="android:colorPrimaryDark">@color/primary_dark</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorAccent">@color/accent</item> diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java index 0ee970d6db3a..91ac0334d4be 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java @@ -358,18 +358,6 @@ public abstract class BaseActivity extends Activity { return mState; } - public static abstract class DocumentsIntent { - /** Intent action name to open copy destination. */ - public static String ACTION_OPEN_COPY_DESTINATION = - "com.android.documentsui.OPEN_COPY_DESTINATION"; - - /** - * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which - * specifies if the destination directory needs to create new directory or not. - */ - public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY"; - } - void setDisplayAdvancedDevices(boolean display) { LocalPreferences.setDisplayAdvancedDevices(this, display); mState.showAdvanced = mState.forceAdvanced | display; diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java index 55a123f8445a..55e2f441a882 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java @@ -179,8 +179,7 @@ public class CopyService extends IntentService { if (mFailedFiles.size() > 0) { Log.e(TAG, mFailedFiles.size() + " files failed to copy"); final Context context = getApplicationContext(); - final Intent navigateIntent = new Intent(context, FilesActivity.class); - navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack); + final Intent navigateIntent = buildNavigateIntent(context, stack); navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY); navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode); navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles); @@ -228,8 +227,7 @@ public class CopyService extends IntentService { mIsCancelled = false; final Context context = getApplicationContext(); - final Intent navigateIntent = new Intent(context, FilesActivity.class); - navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack); + final Intent navigateIntent = buildNavigateIntent(context, stack); final String contentTitle = getString(copying ? R.string.copy_notification_title : R.string.move_notification_title); @@ -592,4 +590,14 @@ public class CopyService extends IntentService { } } } + + /** + * Creates an intent for navigating back to the destination directory. + */ + private Intent buildNavigateIntent(Context context, DocumentStack stack) { + Intent intent = new Intent(context, FilesActivity.class); + intent.setAction(DocumentsContract.ACTION_BROWSE); + intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack); + return intent; + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 13c481c14dcf..e965050ad54c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -19,7 +19,7 @@ package com.android.documentsui; import static com.android.documentsui.State.ACTION_CREATE; import static com.android.documentsui.State.ACTION_GET_CONTENT; import static com.android.documentsui.State.ACTION_OPEN; -import static com.android.documentsui.State.ACTION_OPEN_COPY_DESTINATION; +import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION; import static com.android.documentsui.State.ACTION_OPEN_TREE; import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE; @@ -123,7 +123,7 @@ public class DocumentsActivity extends BaseActivity { final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); SaveFragment.show(getFragmentManager(), mimeType, title); } else if (mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { PickFragment.show(getFragmentManager()); } @@ -135,7 +135,7 @@ public class DocumentsActivity extends BaseActivity { } else if (mState.action == ACTION_OPEN || mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { RootsFragment.show(getFragmentManager(), null); } @@ -163,8 +163,8 @@ public class DocumentsActivity extends BaseActivity { state.action = ACTION_GET_CONTENT; } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) { state.action = ACTION_OPEN_TREE; - } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) { - state.action = ACTION_OPEN_COPY_DESTINATION; + } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) { + state.action = ACTION_PICK_COPY_DESTINATION; } if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) { @@ -172,9 +172,9 @@ public class DocumentsActivity extends BaseActivity { Intent.EXTRA_ALLOW_MULTIPLE, false); } - if (state.action == ACTION_OPEN_COPY_DESTINATION) { + if (state.action == ACTION_PICK_COPY_DESTINATION) { state.directoryCopy = intent.getBooleanExtra( - BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false); + Shared.EXTRA_DIRECTORY_COPY, false); state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_COPY); } @@ -257,7 +257,7 @@ public class DocumentsActivity extends BaseActivity { mState.action == ACTION_OPEN_TREE) { mRootsToolbar.setTitle(R.string.title_open); } else if (mState.action == ACTION_CREATE || - mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { mRootsToolbar.setTitle(R.string.title_save); } } @@ -324,7 +324,7 @@ public class DocumentsActivity extends BaseActivity { boolean recents = cwd == null; boolean picking = mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE - || mState.action == ACTION_OPEN_COPY_DESTINATION; + || mState.action == ACTION_PICK_COPY_DESTINATION; createDir.setVisible(picking && !recents && cwd.isCreateSupported()); mSearchManager.showMenu(!picking); @@ -361,7 +361,7 @@ public class DocumentsActivity extends BaseActivity { // No directory means recents if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { RecentsCreateFragment.show(fm); } else { DirectoryFragment.showRecentsOpen(fm, anim); @@ -391,7 +391,7 @@ public class DocumentsActivity extends BaseActivity { } if (mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { final PickFragment pick = PickFragment.get(fm); if (pick != null) { pick.setPickTarget(mState.action, mState.transferMode, cwd); @@ -444,7 +444,7 @@ public class DocumentsActivity extends BaseActivity { if (mState.action == ACTION_OPEN_TREE) { result = DocumentsContract.buildTreeDocumentUri( pickTarget.authority, pickTarget.documentId); - } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) { + } else if (mState.action == ACTION_PICK_COPY_DESTINATION) { result = pickTarget.derivedUri; } else { // Should not be reached. @@ -461,7 +461,7 @@ public class DocumentsActivity extends BaseActivity { final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack); if (mState.action == ACTION_CREATE || mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_OPEN_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { // Remember stack for last create values.clear(); values.put(RecentColumns.KEY, mState.stack.buildKey()); @@ -500,7 +500,7 @@ public class DocumentsActivity extends BaseActivity { | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) { + } else if (mState.action == ACTION_PICK_COPY_DESTINATION) { // Picking a copy destination is only used internally by us, so we // don't need to extend permissions to the caller. intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack); diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java index 48e28dcfdd5c..bbf4682a1466 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java @@ -110,7 +110,7 @@ public class PickFragment extends Fragment { mPick.setText(R.string.button_select); mCancel.setVisibility(View.GONE); break; - case State.ACTION_OPEN_COPY_DESTINATION: + case State.ACTION_PICK_COPY_DESTINATION: mPick.setText(R.string.button_copy); mCancel.setVisibility(View.VISIBLE); break; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index 4fc378875b74..72ee6cbab5fd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -360,7 +360,7 @@ public class RootsCache { // Exclude read-only devices when creating if (state.action == State.ACTION_CREATE && !supportsCreate) continue; - if (state.action == State.ACTION_OPEN_COPY_DESTINATION && !supportsCreate) continue; + if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue; // Exclude roots that don't support directory picking if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue; // Exclude advanced devices when not requested diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java index a4d6dc57cd34..570c9bfee1b1 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java @@ -20,6 +20,16 @@ import android.content.Context; /** @hide */ public final class Shared { + /** Intent action name to pick a copy destination. */ + public static final String ACTION_PICK_COPY_DESTINATION = + "com.android.documentsui.PICK_COPY_DESTINATION"; + + /** + * Extra boolean flag for {@link ACTION_PICK_COPY_DESTINATION}, which + * specifies if the destination directory needs to create new directory or not. + */ + public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY"; + public static final boolean DEBUG = true; public static final String TAG = "Documents"; public static final String EXTRA_STACK = "com.android.documentsui.STACK"; diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java index 4306a0e024a2..49a1e6625c9b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/State.java +++ b/packages/DocumentsUI/src/com/android/documentsui/State.java @@ -75,7 +75,7 @@ public class State implements android.os.Parcelable { public static final int ACTION_OPEN_TREE = 4; public static final int ACTION_MANAGE = 5; public static final int ACTION_BROWSE = 6; - public static final int ACTION_OPEN_COPY_DESTINATION = 8; + public static final int ACTION_PICK_COPY_DESTINATION = 8; public static final int MODE_UNKNOWN = 0; public static final int MODE_LIST = 1; @@ -150,4 +150,4 @@ public class State implements android.os.Parcelable { return new State[size]; } }; -}
\ No newline at end of file +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 21420c873ecc..8b3893f36cff 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -111,8 +111,8 @@ import com.android.documentsui.Snackbars; import com.android.documentsui.State; import com.android.documentsui.ThumbnailCache; import com.android.documentsui.BaseActivity.DocumentContext; -import com.android.documentsui.BaseActivity.DocumentsIntent; import com.android.documentsui.ProviderExecutor.Preemptable; +import com.android.documentsui.Shared; import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.dirlist.MultiSelectManager.Callback; import com.android.documentsui.dirlist.MultiSelectManager.Selection; @@ -897,7 +897,7 @@ public class DirectoryFragment extends Fragment { // Pop up a dialog to pick a destination. This is inadequate but works for now. // TODO: Implement a picker that is to spec. final Intent intent = new Intent( - BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION, + Shared.ACTION_PICK_COPY_DESTINATION, Uri.EMPTY, getActivity(), DocumentsActivity.class); @@ -914,7 +914,7 @@ public class DirectoryFragment extends Fragment { break; } } - intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy); + intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy); intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode); startActivityForResult(intent, REQUEST_COPY_DESTINATION); } @@ -943,7 +943,6 @@ public class DirectoryFragment extends Fragment { public void setSelected(boolean selected) { itemView.setActivated(selected); - itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor); } @Override @@ -1080,8 +1079,6 @@ public class DirectoryFragment extends Fragment { holder.setSelected(isSelected(position)); - final View line2 = itemView.findViewById(R.id.line2); - final ImageView iconMime = (ImageView) itemView.findViewById(R.id.icon_mime); final ImageView iconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb); final TextView title = (TextView) itemView.findViewById(android.R.id.title); @@ -1138,14 +1135,11 @@ public class DirectoryFragment extends Fragment { getDocumentIcon(mContext, docAuthority, docId, docMimeType, docIcon, state)); } - boolean hasLine2 = false; - - final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles; - if (!hideTitle) { + if ((state.derivedMode == MODE_GRID) && mHideGridTitles) { + title.setVisibility(View.GONE); + } else { title.setText(docDisplayName); title.setVisibility(View.VISIBLE); - } else { - title.setVisibility(View.GONE); } Drawable iconDrawable = null; @@ -1161,7 +1155,6 @@ public class DirectoryFragment extends Fragment { if (alwaysShowSummary) { summary.setText(root.getDirectoryString()); summary.setVisibility(View.VISIBLE); - hasLine2 = true; } else { if (iconDrawable != null && roots.isIconUniqueBlocking(root)) { // No summary needed if icon speaks for itself @@ -1170,7 +1163,6 @@ public class DirectoryFragment extends Fragment { summary.setText(root.getDirectoryString()); summary.setVisibility(View.VISIBLE); summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END); - hasLine2 = true; } } } @@ -1187,48 +1179,37 @@ public class DirectoryFragment extends Fragment { if (docSummary != null) { summary.setText(docSummary); summary.setVisibility(View.VISIBLE); - hasLine2 = true; } else { summary.setVisibility(View.INVISIBLE); } } } - if (icon1 != null) icon1.setVisibility(View.GONE); - if (iconDrawable != null) { icon1.setVisibility(View.VISIBLE); icon1.setImageDrawable(iconDrawable); + } else { + icon1.setVisibility(View.GONE); } if (docLastModified == -1) { date.setText(null); } else { date.setText(formatTime(mContext, docLastModified)); - hasLine2 = true; } - if (state.showSize) { - size.setVisibility(View.VISIBLE); - if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) { - size.setText(null); - } else { - size.setText(Formatter.formatFileSize(mContext, docSize)); - hasLine2 = true; - } - } else { + if (!state.showSize || Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) { size.setVisibility(View.GONE); - } - - if (line2 != null) { - line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE); + } else { + size.setVisibility(View.VISIBLE); + size.setText(Formatter.formatFileSize(mContext, docSize)); } setEnabledRecursive(itemView, enabled); iconMime.setAlpha(iconAlpha); iconThumb.setAlpha(iconAlpha); - if (icon1 != null) icon1.setAlpha(iconAlpha); + icon1.setAlpha(iconAlpha); if (DEBUG_ENABLE_DND) { setupDragAndDropOnDocumentView(itemView, cursor); diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java index 0963845db628..1135c215660e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java @@ -25,6 +25,8 @@ import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.RecyclerView; import android.util.TypedValue; +import com.android.documentsui.R; + import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -43,12 +45,8 @@ class DirectoryItemAnimator extends DefaultItemAnimator { private final Integer mSelectedColor; public DirectoryItemAnimator(Context context) { - mDefaultColor = context.getResources().getColor(android.R.color.transparent); - // Get the accent color. - TypedValue selColor = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true); - // Set the opacity to 10%. - mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000; + mDefaultColor = context.getResources().getColor(R.color.item_doc_background); + mSelectedColor = context.getResources().getColor(R.color.item_doc_background_selected); } @Override diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index f0f816173586..7c0676f23a94 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -31,6 +31,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; @@ -59,6 +60,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private MtpManager mMtpManager; private ContentResolver mResolver; + @GuardedBy("mDeviceToolkits") private Map<Integer, DeviceToolkit> mDeviceToolkits; private RootScanner mRootScanner; private Resources mResources; @@ -222,9 +224,11 @@ public class MtpDocumentsProvider extends DocumentsProvider { @Override public void onTrimMemory(int level) { - for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { - toolkit.mDocumentLoader.clearCompletedTasks(); - } + synchronized (mDeviceToolkits) { + for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { + toolkit.mDocumentLoader.clearCompletedTasks(); + } + } } @Override @@ -254,21 +258,28 @@ public class MtpDocumentsProvider extends DocumentsProvider { } void openDevice(int deviceId) throws IOException { - mMtpManager.openDevice(deviceId); - mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); - mRootScanner.scanNow(); + synchronized (mDeviceToolkits) { + mMtpManager.openDevice(deviceId); + mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); + } + mRootScanner.resume(); } - void closeDevice(int deviceId) throws IOException { + void closeDevice(int deviceId) throws IOException, InterruptedException { // TODO: Flush the device before closing (if not closed externally). - getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); - mDeviceToolkits.remove(deviceId); - mDatabase.removeDeviceRows(deviceId); - mMtpManager.closeDevice(deviceId); + synchronized (mDeviceToolkits) { + getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); + mDeviceToolkits.remove(deviceId); + mDatabase.removeDeviceRows(deviceId); + mMtpManager.closeDevice(deviceId); + } mRootScanner.notifyChange(); + if (!hasOpenedDevices()) { + mRootScanner.pause(); + } } - void close() throws InterruptedException { + synchronized void closeAllDevices() throws InterruptedException { boolean closed = false; for (int deviceId : mMtpManager.getOpenedDeviceIds()) { try { @@ -282,15 +293,30 @@ public class MtpDocumentsProvider extends DocumentsProvider { } if (closed) { mRootScanner.notifyChange(); + mRootScanner.pause(); } - mRootScanner.close(); - mDatabase.close(); } boolean hasOpenedDevices() { return mMtpManager.getOpenedDeviceIds().length != 0; } + /** + * Finalize the content provider for unit tests. + */ + @Override + public void shutdown() { + try { + closeAllDevices(); + } catch (InterruptedException e) { + // It should fail unit tests by throwing runtime exception. + throw new RuntimeException(e.getMessage()); + } finally { + mDatabase.close(); + super.shutdown(); + } + } + private void notifyChildDocumentsChange(String parentDocumentId) { mResolver.notifyChange( DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId), @@ -299,11 +325,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { } private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException { - final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); - if (toolkit == null) { - throw new FileNotFoundException(); + synchronized (mDeviceToolkits) { + final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId); + if (toolkit == null) { + throw new FileNotFoundException(); + } + return toolkit; } - return toolkit; } private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java index 2d1af26ba3d2..723dc149c15d 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java @@ -67,10 +67,10 @@ public class MtpDocumentsService extends Service { provider.openDevice(device.getDeviceId()); return START_STICKY; } catch (IOException error) { - Log.d(MtpDocumentsProvider.TAG, error.getMessage()); + Log.e(MtpDocumentsProvider.TAG, error.getMessage()); } } else { - Log.d(MtpDocumentsProvider.TAG, "Received unknown intent action."); + Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action."); } stopSelfIfNeeded(); return Service.START_NOT_STICKY; @@ -82,7 +82,7 @@ public class MtpDocumentsService extends Service { unregisterReceiver(mReceiver); mReceiver = null; try { - provider.close(); + provider.closeAllDevices(); } catch (InterruptedException e) { Log.e(MtpDocumentsProvider.TAG, e.getMessage()); } @@ -105,8 +105,8 @@ public class MtpDocumentsService extends Service { final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); try { provider.closeDevice(device.getDeviceId()); - } catch (IOException error) { - Log.d(MtpDocumentsProvider.TAG, error.getMessage()); + } catch (IOException | InterruptedException error) { + Log.e(MtpDocumentsProvider.TAG, error.getMessage()); } stopSelfIfNeeded(); } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java index d9ed4ab5dc4a..b0962dd148b9 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java @@ -9,6 +9,10 @@ import android.provider.DocumentsContract; import android.util.Log; import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; final class RootScanner { /** @@ -27,13 +31,18 @@ final class RootScanner { */ private final static long SHORT_POLLING_TIMES = 10; + /** + * Milliseconds we wait for background thread when pausing. + */ + private final static long AWAIT_TERMINATION_TIMEOUT = 2000; + final ContentResolver mResolver; final Resources mResources; final MtpManager mManager; final MtpDatabase mDatabase; - boolean mClosed = false; - int mPollingCount; - Thread mBackgroundThread; + + ExecutorService mExecutor; + FutureTask<Void> mCurrentTask; RootScanner( ContentResolver resolver, @@ -46,6 +55,9 @@ final class RootScanner { mDatabase = database; } + /** + * Notifies a change of the roots list via ContentResolver. + */ void notifyChange() { final Uri uri = DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); @@ -56,74 +68,81 @@ final class RootScanner { * Starts to check new changes right away. * If the background thread has already gone, it restarts another background thread. */ - synchronized void scanNow() { - if (mClosed) { - return; + synchronized void resume() { + if (mExecutor == null) { + // Only single thread updates the database. + mExecutor = Executors.newSingleThreadExecutor(); } - mPollingCount = 0; - if (mBackgroundThread == null) { - mBackgroundThread = new BackgroundLoaderThread(); - mBackgroundThread.start(); - } else { - notify(); + if (mCurrentTask != null) { + // Cancel previous task. + mCurrentTask.cancel(true); } + mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null); + mExecutor.submit(mCurrentTask); } - void close() throws InterruptedException { - Thread thread; - synchronized (this) { - mClosed = true; - thread = mBackgroundThread; - if (mBackgroundThread == null) { - return; - } - notify(); + /** + * Stops background thread and wait for its termination. + * @throws InterruptedException + */ + synchronized void pause() throws InterruptedException { + if (mExecutor == null) { + return; + } + mExecutor.shutdownNow(); + if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) { + Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread."); } - thread.join(); + mExecutor = null; } - private final class BackgroundLoaderThread extends Thread { + /** + * Runnable to scan roots and update the database information. + */ + private final class UpdateRootsRunnable implements Runnable { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - synchronized (RootScanner.this) { - while (!mClosed) { - final int[] deviceIds = mManager.getOpenedDeviceIds(); - if (deviceIds.length == 0) { - break; - } - boolean changed = false; - for (int deviceId : deviceIds) { + int pollingCount = 0; + while (!Thread.interrupted()) { + final int[] deviceIds = mManager.getOpenedDeviceIds(); + if (deviceIds.length == 0) { + return; + } + boolean changed = false; + for (int deviceId : deviceIds) { + try { + final MtpRoot[] roots = mManager.getRoots(deviceId); mDatabase.startAddingRootDocuments(deviceId); try { - changed = mDatabase.putRootDocuments( - deviceId, mResources, mManager.getRoots(deviceId)) || changed; - } catch (IOException|SQLiteException exp) { - // The error may happen on the device. We would like to continue getting - // roots for other devices. - Log.e(MtpDocumentsProvider.TAG, exp.getMessage()); - continue; + if (mDatabase.putRootDocuments(deviceId, mResources, roots)) { + changed = true; + } } finally { - changed = mDatabase.stopAddingRootDocuments(deviceId) || changed; + if (mDatabase.stopAddingRootDocuments(deviceId)) { + changed = true; + } } - } - if (changed) { - notifyChange(); - } - mPollingCount++; - try { - // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is - // more likely to add new root just after the device is added. - // TODO: Use short interval only for a device that is just added. - RootScanner.this.wait( - mPollingCount > SHORT_POLLING_TIMES ? - LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL); - } catch (InterruptedException exception) { - break; + } catch (IOException | SQLiteException exception) { + // The error may happen on the device. We would like to continue getting + // roots for other devices. + Log.e(MtpDocumentsProvider.TAG, exception.getMessage()); } } - - mBackgroundThread = null; + if (changed) { + notifyChange(); + } + pollingCount++; + try { + // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is + // more likely to add new root just after the device is added. + // TODO: Use short interval only for a device that is just added. + Thread.sleep(pollingCount > SHORT_POLLING_TIMES ? + LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL); + } catch (InterruptedException exp) { + // The while condition handles the interrupted flag. + continue; + } } } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 82e08cd26927..cabb08de9a04 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -49,11 +49,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { @Override public void tearDown() { - try { - mProvider.close(); - } catch (InterruptedException e) { - fail(); - } + mProvider.shutdown(); } public void testOpenAndCloseDevice() throws Exception { diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index c7cf61ae5559..5a6f1d1f1a31 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -61,7 +61,7 @@ <activity android:name=".ui.PrintActivity" - android:configChanges="orientation|screenSize" + android:configChanges="screenSize|smallestScreenSize|orientation" android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE" android:theme="@style/PrintActivity"> <intent-filter> diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml index 3db0848ed632..4469d387853c 100644 --- a/packages/Shell/res/values/strings.xml +++ b/packages/Shell/res/values/strings.xml @@ -33,4 +33,8 @@ <!-- Title for documents backend that offers bugreports. --> <string name="bugreport_storage_title">Bug reports</string> + + <!-- Toast message sent when the bugreport file could be read. --> + <string name="bugreport_unreadable_text">Bug report file could not be read</string> + </resources> diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java index e90a3b5a4824..c26437285050 100644 --- a/packages/Shell/src/com/android/shell/BugreportReceiver.java +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -37,6 +37,7 @@ import android.support.v4.content.FileProvider; import android.text.format.DateUtils; import android.util.Log; import android.util.Patterns; +import android.widget.Toast; import com.google.android.collect.Lists; import libcore.io.Streams; @@ -105,6 +106,13 @@ public class BugreportReceiver extends BroadcastReceiver { */ private void triggerLocalNotification(final Context context, final File bugreportFile, final File screenshotFile) { + if (!bugreportFile.exists() || !bugreportFile.canRead()) { + Log.e(TAG, "Could not read bugreport file " + bugreportFile); + Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text), + Toast.LENGTH_LONG).show(); + return; + } + boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt"); if (!isPlainText) { // Already zipped, send it right away. @@ -141,10 +149,12 @@ public class BugreportReceiver extends BroadcastReceiver { intent.putExtra(Intent.EXTRA_TEXT, messageBody); final ClipData clipData = new ClipData(null, new String[] { mimeType }, new ClipData.Item(null, null, null, bugreportUri)); - clipData.addItem(new ClipData.Item(null, null, null, screenshotUri)); + final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri); + if (screenshotUri != null) { + clipData.addItem(new ClipData.Item(null, null, null, screenshotUri)); + attachments.add(screenshotUri); + } intent.setClipData(clipData); - - final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); final Account sendToAccount = findSendToAccount(context); @@ -162,8 +172,8 @@ public class BugreportReceiver extends BroadcastReceiver { File screenshotFile) { // Files are kept on private storage, so turn into Uris that we can // grant temporary permissions for. - final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile); - final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile); + final Uri bugreportUri = getUri(context, bugreportFile); + final Uri screenshotUri = getUri(context, screenshotFile); Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri); Intent notifIntent; @@ -272,6 +282,10 @@ public class BugreportReceiver extends BroadcastReceiver { return foundAccount; } + private static Uri getUri(Context context, File file) { + return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null; + } + private static File getFileExtra(Intent intent, String key) { final String path = intent.getStringExtra(key); if (path != null) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index fe67fd94fff9..965e7a67489f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -239,7 +239,8 @@ class BackgroundTaskLoader implements Runnable { SystemServicesProxy ssp, Resources res) { Bitmap tdIcon = iconBitmap != null ? iconBitmap - : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename); + : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename, + taskKey.userId); if (tdIcon != null) { return ssp.getBadgedIcon(new BitmapDrawable(res, tdIcon), taskKey.userId); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java index 4ecb80a22f36..f3c66a54a360 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -17,6 +17,8 @@ package com.android.systemui.recents.views; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.StackId; import android.app.ActivityOptions; import android.content.Context; import android.graphics.Bitmap; @@ -31,7 +33,6 @@ import android.view.AppTransitionAnimationSpec; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; -import com.android.systemui.recents.Constants; import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsDebugFlags; @@ -48,6 +49,7 @@ import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; import java.util.List; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; 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; @@ -93,7 +95,7 @@ public class RecentsTransitionHelper { final boolean lockToTask, final Rect bounds, int destinationStack) { final ActivityOptions opts = ActivityOptions.makeBasic(); if (bounds != null) { - opts.setBounds(bounds.isEmpty() ? null : bounds); + opts.setLaunchBounds(bounds.isEmpty() ? null : bounds); } final ActivityOptions.OnAnimationStartedListener animStartedListener; @@ -244,8 +246,7 @@ public class RecentsTransitionHelper { // Ensure we have a valid target stack id final int targetStackId = destinationStack != INVALID_STACK_ID ? destinationStack : task.key.stackId; - if (targetStackId != FREEFORM_WORKSPACE_STACK_ID - && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) { + if (!StackId.useAnimationSpecForAppTransition(targetStackId)) { return null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index d3d9bef0b703..f8ab35fe0991 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -2161,13 +2161,10 @@ public abstract class BaseStatusBar extends SystemUI implements boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; boolean isFullscreen = notification.fullScreenIntent != null; boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); - boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, - Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; boolean accessibilityForcesLaunch = isFullscreen && mAccessibilityManager.isTouchExplorationEnabled(); boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent(); boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) - && isAllowed && !accessibilityForcesLaunch && !justLaunchedFullScreenIntent && mPowerManager.isScreenOn() diff --git a/preloaded-classes b/preloaded-classes index d6b4ec9efd0e..e48255c1efa7 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -1566,6 +1566,7 @@ android.security.keystore.AndroidKeyStoreBCWorkaroundProvider android.security.keystore.AndroidKeyStoreKey android.security.keystore.AndroidKeyStoreProvider android.security.keystore.KeyStoreCryptoOperation +android.security.net.config.NetworkSecurityConfigProvider android.service.persistentdata.PersistentDataBlockManager android.system.ErrnoException android.system.GaiException diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index c131628f2854..a5cef1a36ef0 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -47,7 +47,9 @@ import android.os.Handler; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.ShellCommand; import android.os.UserHandle; import android.os.storage.MountServiceInternal; import android.util.ArrayMap; @@ -1554,15 +1556,300 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private void dumpHelp(PrintWriter pw) { - pw.println("AppOps service (appops) dump options:"); - pw.println(" [-h] [CMD]"); - pw.println(" -h: print this help text."); - pw.println("Commands:"); + static class Shell extends ShellCommand { + final IAppOpsService mInterface; + final AppOpsService mInternal; + + int userId = UserHandle.USER_SYSTEM; + String packageName; + String opStr; + int op; + int packageUid; + + Shell(IAppOpsService iface, AppOpsService internal) { + mInterface = iface; + mInternal = internal; + } + + @Override + public int onCommand(String cmd) { + return onShellCommand(this, cmd); + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + dumpCommandHelp(pw); + } + + private int strOpToOp(String op, PrintWriter err) { + try { + return AppOpsManager.strOpToOp(op); + } catch (IllegalArgumentException e) { + } + try { + return Integer.parseInt(op); + } catch (NumberFormatException e) { + } + try { + return AppOpsManager.strDebugOpToOp(op); + } catch (IllegalArgumentException e) { + err.println("Error: " + e.getMessage()); + return -1; + } + } + + int parseUserPackageOp(boolean reqOp, PrintWriter err) throws RemoteException { + userId = UserHandle.USER_CURRENT; + packageName = null; + opStr = null; + for (String argument; (argument = getNextArg()) != null;) { + if ("--user".equals(argument)) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + if (packageName == null) { + packageName = argument; + } else if (opStr == null) { + opStr = argument; + break; + } + } + } + if (packageName == null) { + err.println("Error: Package name not specified."); + return -1; + } else if (opStr == null && reqOp) { + err.println("Error: Operation not specified."); + return -1; + } + if (opStr != null) { + op = strOpToOp(opStr, err); + if (op < 0) { + return -1; + } + } else { + op = AppOpsManager.OP_NONE; + } + if (userId == UserHandle.USER_CURRENT) { + userId = ActivityManager.getCurrentUser(); + } + if ("root".equals(packageName)) { + packageUid = 0; + } else { + packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, userId); + } + if (packageUid < 0) { + err.println("Error: No UID for " + packageName + " in user " + userId); + return -1; + } + return 0; + } + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ResultReceiver resultReceiver) { + (new Shell(this, this)).exec(this, in, out, err, args, resultReceiver); + } + + static void dumpCommandHelp(PrintWriter pw) { + pw.println("AppOps service (appops) commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" set [--user <USER_ID>] <PACKAGE> <OP> <MODE>"); + pw.println(" Set the mode for a particular application and operation."); + pw.println(" get [--user <USER_ID>] <PACKAGE> [<OP>]"); + pw.println(" Return the mode for a particular application and optional operation."); + pw.println(" reset [--user <USER_ID>] [<PACKAGE>]"); + pw.println(" Reset the given application or all applications to default modes."); pw.println(" write-settings"); pw.println(" Immediately write pending changes to storage."); pw.println(" read-settings"); pw.println(" Read the last written settings, replacing current state in RAM."); + pw.println(" options:"); + pw.println(" <PACKAGE> an Android package name."); + pw.println(" <OP> an AppOps operation."); + pw.println(" <MODE> one of allow, ignore, deny, or default"); + pw.println(" <USER_ID> the user id under which the package is installed. If --user is not"); + pw.println(" specified, the current user is assumed."); + } + + static int onShellCommand(Shell shell, String cmd) { + if (cmd == null) { + return shell.handleDefaultCommands(cmd); + } + PrintWriter pw = shell.getOutPrintWriter(); + PrintWriter err = shell.getErrPrintWriter(); + try { + switch (cmd) { + case "set": { + int res = shell.parseUserPackageOp(true, err); + if (res < 0) { + return res; + } + String modeStr = shell.getNextArg(); + if (modeStr == null) { + err.println("Error: Mode not specified."); + return -1; + } + + final int mode; + switch (modeStr) { + case "allow": + mode = AppOpsManager.MODE_ALLOWED; + break; + case "deny": + mode = AppOpsManager.MODE_ERRORED; + break; + case "ignore": + mode = AppOpsManager.MODE_IGNORED; + break; + case "default": + mode = AppOpsManager.MODE_DEFAULT; + break; + default: + err.println("Error: Mode " + modeStr + " is not valid,"); + return -1; + } + + shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName, mode); + return 0; + } + case "get": { + int res = shell.parseUserPackageOp(false, err); + if (res < 0) { + return res; + } + + List<AppOpsManager.PackageOps> ops = shell.mInterface.getOpsForPackage( + shell.packageUid, shell.packageName, + shell.op != AppOpsManager.OP_NONE ? new int[] {shell.op} : null); + if (ops == null || ops.size() <= 0) { + pw.println("No operations."); + return 0; + } + final long now = System.currentTimeMillis(); + for (int i=0; i<ops.size(); i++) { + List<AppOpsManager.OpEntry> entries = ops.get(i).getOps(); + for (int j=0; j<entries.size(); j++) { + AppOpsManager.OpEntry ent = entries.get(j); + pw.print(AppOpsManager.opToName(ent.getOp())); + pw.print(": "); + switch (ent.getMode()) { + case AppOpsManager.MODE_ALLOWED: + pw.print("allow"); + break; + case AppOpsManager.MODE_IGNORED: + pw.print("ignore"); + break; + case AppOpsManager.MODE_ERRORED: + pw.print("deny"); + break; + case AppOpsManager.MODE_DEFAULT: + pw.print("default"); + break; + default: + pw.print("mode="); + pw.print(ent.getMode()); + break; + } + if (ent.getTime() != 0) { + pw.print("; time="); + TimeUtils.formatDuration(now - ent.getTime(), pw); + pw.print(" ago"); + } + if (ent.getRejectTime() != 0) { + pw.print("; rejectTime="); + TimeUtils.formatDuration(now - ent.getRejectTime(), pw); + pw.print(" ago"); + } + if (ent.getDuration() == -1) { + pw.print(" (running)"); + } else if (ent.getDuration() != 0) { + pw.print("; duration="); + TimeUtils.formatDuration(ent.getDuration(), pw); + } + pw.println(); + } + } + return 0; + } + case "reset": { + String packageName = null; + int userId = UserHandle.USER_CURRENT; + for (String argument; (argument = shell.getNextArg()) != null;) { + if ("--user".equals(argument)) { + String userStr = shell.getNextArgRequired(); + userId = UserHandle.parseUserArg(userStr); + } else { + if (packageName == null) { + packageName = argument; + } else { + err.println("Error: Unsupported argument: " + argument); + return -1; + } + } + } + + if (userId == UserHandle.USER_CURRENT) { + userId = ActivityManager.getCurrentUser(); + } + + shell.mInterface.resetAllModes(userId, packageName); + pw.print("Reset all modes for: "); + if (userId == UserHandle.USER_ALL) { + pw.print("all users"); + } else { + pw.print("user "); pw.print(userId); + } + pw.print(", "); + if (packageName == null) { + pw.println("all packages"); + } else { + pw.print("package "); pw.println(packageName); + } + return 0; + } + case "write-settings": { + shell.mInternal.mContext.enforcePermission( + android.Manifest.permission.UPDATE_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), null); + long token = Binder.clearCallingIdentity(); + try { + synchronized (shell.mInternal) { + shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner); + } + shell.mInternal.writeState(); + pw.println("Current settings written."); + } finally { + Binder.restoreCallingIdentity(token); + } + return 0; + } + case "read-settings": { + shell.mInternal.mContext.enforcePermission( + android.Manifest.permission.UPDATE_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), null); + long token = Binder.clearCallingIdentity(); + try { + shell.mInternal.readState(); + pw.println("Last settings read."); + } finally { + Binder.restoreCallingIdentity(token); + } + return 0; + } + default: + return shell.handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + private void dumpHelp(PrintWriter pw) { + pw.println("AppOps service (appops) dump options:"); + pw.println(" none"); } @Override @@ -1583,27 +1870,6 @@ public class AppOpsService extends IAppOpsService.Stub { return; } else if ("-a".equals(arg)) { // dump all data - } else if ("write-settings".equals(arg)) { - long token = Binder.clearCallingIdentity(); - try { - synchronized (this) { - mHandler.removeCallbacks(mWriteRunner); - } - writeState(); - pw.println("Current settings written."); - } finally { - Binder.restoreCallingIdentity(token); - } - return; - } else if ("read-settings".equals(arg)) { - long token = Binder.clearCallingIdentity(); - try { - readState(); - pw.println("Last settings read."); - } finally { - Binder.restoreCallingIdentity(token); - } - return; } else if (arg.length() > 0 && arg.charAt(0) == '-'){ pw.println("Unknown option: " + arg); return; diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 485e26b7752a..f5ed83ed5f1d 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -107,6 +107,8 @@ public class DeviceIdleController extends SystemService private static final boolean COMPRESS_TIME = false; + private static final int EVENT_BUFFER_SIZE = 40; + private static final String ACTION_STEP_IDLE_STATE = "com.android.server.device_idle.STEP_IDLE_STATE"; @@ -196,6 +198,8 @@ public class DeviceIdleController extends SystemService private long mNextIdlePendingDelay; private long mNextIdleDelay; private long mNextLightAlarmTime; + private long mCurIdleBudget; + private long mMaintenanceStartTime; private int mActiveIdleOpCount; private IBinder mDownloadServiceActive; @@ -274,6 +278,25 @@ public class DeviceIdleController extends SystemService */ private int[] mTempWhitelistAppIdArray = new int[0]; + private static final int EVENT_NULL = 0; + private static final int EVENT_NORMAL = 1; + private static final int EVENT_LIGHT_IDLE = 2; + private static final int EVENT_LIGHT_MAINTENANCE = 3; + private static final int EVENT_FULL_IDLE = 4; + private static final int EVENT_FULL_MAINTENANCE = 5; + + private int[] mEventCmds = new int[EVENT_BUFFER_SIZE]; + private long[] mEventTimes = new long[EVENT_BUFFER_SIZE]; + + private void addEvent(int cmd) { + if (mEventCmds[0] != cmd) { + System.arraycopy(mEventCmds, 0, mEventCmds, 1, EVENT_BUFFER_SIZE - 1); + System.arraycopy(mEventTimes, 0, mEventTimes, 1, EVENT_BUFFER_SIZE - 1); + mEventCmds[0] = cmd; + mEventTimes[0] = SystemClock.elapsedRealtime(); + } + } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { @@ -424,7 +447,10 @@ public class DeviceIdleController extends SystemService private final class Constants extends ContentObserver { // Key names stored in the settings value. private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to"; - private static final String KEY_LIGHT_IDLE_PENDING_TIMEOUT = "light_idle_pending_to"; + private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET + = "light_idle_maintenance_min_budget"; + private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET + = "light_idle_maintenance_max_budget"; private static final String KEY_INACTIVE_TIMEOUT = "inactive_to"; private static final String KEY_SENSING_TIMEOUT = "sensing_to"; private static final String KEY_LOCATING_TIMEOUT = "locating_to"; @@ -454,12 +480,24 @@ public class DeviceIdleController extends SystemService public long LIGHT_IDLE_TIMEOUT; /** - * This is the initial time, after light idle idle, that we will will sit in the - * LIGHT_IDLE_MAINTENANCE period for the system to run normally before returning to idle. + * This is the minimum amount of time we want to make available for maintenance mode + * when lightly idling. That is, we will always have at least this amount of time + * available maintenance before timing out and cutting off maintenance mode. * @see Settings.Global#DEVICE_IDLE_CONSTANTS - * @see #KEY_LIGHT_IDLE_PENDING_TIMEOUT + * @see #KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET */ - public long LIGHT_IDLE_PENDING_TIMEOUT; + public long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; + + /** + * This is the maximum amount of time we want to make available for maintenance mode + * when lightly idling. That is, if the system isn't using up its minimum maintenance + * budget and this time is being added to the budget reserve, this is the maximum + * reserve size we will allow to grow and thus the maximum amount of time we will + * allow for the maintenance window. + * @see Settings.Global#DEVICE_IDLE_CONSTANTS + * @see #KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET + */ + public long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET; /** * This is the time, after becoming inactive, at which we start looking at the @@ -619,8 +657,12 @@ public class DeviceIdleController extends SystemService LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT, !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L); - LIGHT_IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_PENDING_TIMEOUT, + LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong( + KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, !COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L); + LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mParser.getLong( + KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, + !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L); INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT, !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L); SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT, @@ -662,8 +704,12 @@ public class DeviceIdleController extends SystemService TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw); pw.println(); - pw.print(" "); pw.print(KEY_LIGHT_IDLE_PENDING_TIMEOUT); pw.print("="); - TimeUtils.formatDuration(LIGHT_IDLE_PENDING_TIMEOUT, pw); + pw.print(" "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("="); + TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET); pw.print("="); + TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, pw); pw.println(); pw.print(" "); pw.print(KEY_INACTIVE_TIMEOUT); pw.print("="); @@ -1435,8 +1481,11 @@ public class DeviceIdleController extends SystemService mState = STATE_ACTIVE; mLightState = LIGHT_STATE_ACTIVE; mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; + mCurIdleBudget = 0; + mMaintenanceStartTime = 0; resetIdleManagementLocked(); resetLightIdleManagementLocked(); + addEvent(EVENT_NORMAL); } } @@ -1496,21 +1545,43 @@ public class DeviceIdleController extends SystemService switch (mLightState) { case LIGHT_STATE_INACTIVE: + mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; + mMaintenanceStartTime = 0; case LIGHT_STATE_IDLE_MAINTENANCE: + if (mMaintenanceStartTime != 0) { + long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime; + if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) { + // We didn't use up all of our minimum budget; add this to the reserve. + mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration); + } else { + // We used more than our minimum budget; this comes out of the reserve. + mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); + } + } + mMaintenanceStartTime = 0; scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT); if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE."); mLightState = LIGHT_STATE_IDLE; EventLogTags.writeDeviceIdleLight(mLightState, reason); + addEvent(EVENT_LIGHT_IDLE); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT); break; case LIGHT_STATE_IDLE: // We have been idling long enough, now it is time to do some work. mActiveIdleOpCount = 1; - scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT); + mMaintenanceStartTime = SystemClock.elapsedRealtime(); + if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) { + mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET; + } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) { + mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET; + } + mMaintenanceStartTime = SystemClock.elapsedRealtime(); + scheduleLightAlarmLocked(mCurIdleBudget); if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE."); mLightState = LIGHT_STATE_IDLE_MAINTENANCE; EventLogTags.writeDeviceIdleLight(mLightState, reason); + addEvent(EVENT_LIGHT_MAINTENANCE); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); break; } @@ -1600,6 +1671,7 @@ public class DeviceIdleController extends SystemService cancelLightAlarmLocked(); } EventLogTags.writeDeviceIdle(mState, reason); + addEvent(EVENT_FULL_IDLE); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON); break; case STATE_IDLE: @@ -1612,6 +1684,7 @@ public class DeviceIdleController extends SystemService (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR)); mState = STATE_IDLE_MAINTENANCE; EventLogTags.writeDeviceIdle(mState, reason); + addEvent(EVENT_FULL_MAINTENANCE); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); break; } @@ -1709,7 +1782,10 @@ public class DeviceIdleController extends SystemService scheduleReportActiveLocked(type, Process.myUid()); mState = STATE_ACTIVE; mInactiveTimeout = timeout; + mCurIdleBudget = 0; + mMaintenanceStartTime = 0; EventLogTags.writeDeviceIdle(mState, type); + addEvent(EVENT_NORMAL); becomeInactive = true; } if (mLightState == LIGHT_STATE_OVERRIDE) { @@ -2016,6 +2092,8 @@ public class DeviceIdleController extends SystemService pw.println(" Print this help text."); pw.println(" step"); pw.println(" Immediately step to next state, without waiting for alarm."); + pw.println(" light-step"); + pw.println(" Immediately step to next light idle state, without waiting for alarm."); pw.println(" force-idle"); pw.println(" Force directly into idle mode, regardless of other device state."); pw.println(" Use \"step\" to get out."); @@ -2262,6 +2340,31 @@ public class DeviceIdleController extends SystemService synchronized (this) { mConstants.dump(pw); + if (mEventCmds[0] != EVENT_NULL) { + pw.println(" Idling history:"); + long now = SystemClock.elapsedRealtime(); + for (int i=EVENT_BUFFER_SIZE-1; i>=0; i--) { + int cmd = mEventCmds[i]; + if (cmd == EVENT_NULL) { + continue; + } + String label; + switch (mEventCmds[i]) { + case EVENT_NORMAL: label = " normal"; break; + case EVENT_LIGHT_IDLE: label = " light-idle"; break; + case EVENT_LIGHT_MAINTENANCE: label = "light-maint"; break; + case EVENT_FULL_IDLE: label = " full-idle"; break; + case EVENT_FULL_MAINTENANCE: label = " full-maint"; break; + default: label = " ??"; break; + } + pw.print(" "); + pw.print(label); + pw.print(": "); + TimeUtils.formatDuration(mEventTimes[i], now, pw);; + pw.println(); + } + } + int size = mPowerSaveWhitelistAppsExceptIdle.size(); if (size > 0) { pw.println(" Whitelist (except idle) system apps:"); @@ -2373,6 +2476,16 @@ public class DeviceIdleController extends SystemService TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw); pw.println(); } + if (mCurIdleBudget != 0) { + pw.print(" mCurIdleBudget="); + TimeUtils.formatDuration(mCurIdleBudget, pw); + pw.println(); + } + if (mMaintenanceStartTime != 0) { + pw.print(" mMaintenanceStartTime="); + TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw); + pw.println(); + } if (mSyncActive) { pw.print(" mSyncActive="); pw.println(mSyncActive); } diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index ae9ddc0fcaa7..40f7c5c5586c 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -1196,6 +1196,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ClientState cs = mClients.remove(client.asBinder()); if (cs != null) { clearClientSessionLocked(cs); + if (mCurClient == cs) { + mCurClient = null; + } } } } diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java index 6acec6bb864d..eb49a78ba8f9 100644 --- a/services/core/java/com/android/server/LockSettingsStorage.java +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -389,7 +389,7 @@ class LockSettingsStorage { private int getUserParentOrSelfId(int userId) { // Device supports per user encryption, so lock is applied to the given user. - if (mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) { + if (StorageManager.isFileBasedEncryptionEnabled()) { return userId; } // Device uses Block Based Encryption, and the parent user's lock is used for the whole diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index f89155d93d23..a32bb2f213bd 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -1904,16 +1904,18 @@ class MountService extends IMountService.Stub enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); - synchronized (mLock) { - if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) { + if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) { + final boolean emulateFbe = (flags & StorageManager.DEBUG_EMULATE_FBE) != 0; + SystemProperties.set(StorageManager.PROP_EMULATE_FBE, Boolean.toString(emulateFbe)); + } + + if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) { + synchronized (mLock) { mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0; - } - if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) { - // TODO: persist through vold and reboot - } - writeSettingsLocked(); - mHandler.obtainMessage(H_RESET).sendToTarget(); + writeSettingsLocked(); + mHandler.obtainMessage(H_RESET).sendToTarget(); + } } } @@ -2738,7 +2740,7 @@ class MountService extends IMountService.Stub @Override public boolean isUserKeyUnlocked(int userId) { - if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) { + if (StorageManager.isFileBasedEncryptionEnabled()) { synchronized (mLock) { return ArrayUtils.contains(mUnlockedUsers, userId); } @@ -2761,14 +2763,6 @@ class MountService extends IMountService.Stub } @Override - public boolean isPerUserEncryptionEnabled() { - // TODO: switch this over to a single property; currently using two to - // handle the emulated case - return "file".equals(SystemProperties.get("ro.crypto.type", "none")) - || SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false); - } - - @Override public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException { // TODO: Invoke vold to mount app fuse. throw new UnsupportedOperationException(); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 9ac4ba30bb95..37d47c3b0568 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -88,6 +88,7 @@ import com.google.android.collect.Sets; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; @@ -114,6 +115,7 @@ import java.util.concurrent.atomic.AtomicReference; public class AccountManagerService extends IAccountManager.Stub implements RegisteredServicesCacheListener<AuthenticatorDescription> { + private static final String TAG = "AccountManagerService"; private static final String DATABASE_NAME = "accounts.db"; @@ -2283,6 +2285,193 @@ public class AccountManagerService } } + @Override + public void startAddAccountSession(final IAccountManagerResponse response, final String accountType, + final String authTokenType, final String[] requiredFeatures, + final boolean expectActivityLaunch, + final Bundle optionsIn) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "startAddAccountSession: accountType " + accountType + + ", response " + response + + ", authTokenType " + authTokenType + + ", requiredFeatures " + stringArrayToString(requiredFeatures) + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (response == null) { + throw new IllegalArgumentException("response is null"); + } + if (accountType == null) { + throw new IllegalArgumentException("accountType is null"); + } + + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId)) { + try { + response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED, + "User is not allowed to add an account!"); + } catch (RemoteException re) { + } + showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED, userId); + return; + } + if (!canUserModifyAccountsForType(userId, accountType)) { + try { + response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + "User cannot modify accounts of this type (policy)."); + } catch (RemoteException re) { + } + showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + userId); + return; + } + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; + options.putInt(AccountManager.KEY_CALLER_UID, uid); + options.putInt(AccountManager.KEY_CALLER_PID, pid); + + int usrId = UserHandle.getCallingUserId(); + long identityToken = clearCallingIdentity(); + try { + UserAccounts accounts = getUserAccounts(usrId); + logRecordWithUid(accounts, DebugDbHelper.ACTION_CALLED_START_ACCOUNT_ADD, + TABLE_ACCOUNTS, uid); + new StartAccountSession(accounts, response, accountType, expectActivityLaunch, + null /* accountName */, false /* authDetailsRequired */, + true /* updateLastAuthenticationTime */) { + @Override + public void run() throws RemoteException { + mAuthenticator.startAddAccountSession(this, mAccountType, authTokenType, + requiredFeatures, options); + } + + @Override + protected String toDebugString(long now) { + String requiredFeaturesStr = TextUtils.join(",", requiredFeatures); + return super.toDebugString(now) + ", startAddAccountSession" + ", accountType " + + accountType + ", requiredFeatures " + + (requiredFeatures != null ? requiredFeaturesStr : null); + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */ + private abstract class StartAccountSession extends Session { + + public StartAccountSession(UserAccounts accounts, IAccountManagerResponse response, + String accountType, boolean expectActivityLaunch, String accountName, + boolean authDetailsRequired, boolean updateLastAuthenticationTime) { + super(accounts, response, accountType, expectActivityLaunch, + true /* stripAuthTokenFromResult */, accountName, authDetailsRequired, + updateLastAuthenticationTime); + } + + @Override + public void onResult(Bundle result) { + mNumResults++; + Intent intent = null; + + if (result != null + && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + /* + * The Authenticator API allows third party authenticators to + * supply arbitrary intents to other apps that they can run, + * this can be very bad when those apps are in the system like + * the System Settings. + */ + int authenticatorUid = Binder.getCallingUid(); + long bid = Binder.clearCallingIdentity(); + try { + PackageManager pm = mContext.getPackageManager(); + ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); + int targetUid = resolveInfo.activityInfo.applicationInfo.uid; + if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid, + targetUid)) { + throw new SecurityException("Activity to be started with KEY_INTENT must " + + "share Authenticator's signatures"); + } + } finally { + Binder.restoreCallingIdentity(bid); + } + } + + IAccountManagerResponse response; + if (mExpectActivityLaunch && result != null + && result.containsKey(AccountManager.KEY_INTENT)) { + response = mResponse; + } else { + response = getResponseAndClose(); + } + if (response == null) { + return; + } + if (result == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, getClass().getSimpleName() + " calling onError() on response " + + response); + } + sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE, + "null bundle returned"); + return; + } + + if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && (intent == null)) { + // All AccountManager error codes are greater + // than 0 + sendErrorResponse(response, result.getInt(AccountManager.KEY_ERROR_CODE), + result.getString(AccountManager.KEY_ERROR_MESSAGE)); + return; + } + + // Strip auth token from result. + result.remove(AccountManager.KEY_AUTHTOKEN); + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + getClass().getSimpleName() + " calling onResult() on response " + response); + } + + // Get the session bundle created by authenticator. The + // bundle contains data necessary for finishing the session + // later. The session bundle will be encrypted here and + // decrypted later when trying to finish the session. + Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE); + if (sessionBundle != null) { + String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (TextUtils.isEmpty(accountType) + && !mAccountType.equalsIgnoreCase(mAccountType)) { + Log.w(TAG, "Account type in session bundle doesn't match request."); + } + // Add accountType info to session bundle. This will + // override any value set by authenticator. + sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType); + + // Encrypt session bundle before returning to caller. + try { + CryptoHelper cryptoHelper = CryptoHelper.getInstance(); + Bundle encryptedBundle = cryptoHelper.encryptBundle(sessionBundle); + result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, encryptedBundle); + } catch (GeneralSecurityException e) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.v(TAG, "Failed to encrypt session bundle!", e); + } + sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE, + "failed to encrypt session bundle"); + return; + } + } + + sendResponse(response, result); + } + } + private void showCantAddAccount(int errorCode, int userId) { Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class); cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode); @@ -3336,6 +3525,11 @@ public class AccountManagerService private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add"; private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove"; + // TODO: This action doesn't add account to accountdb. Account is only + // added in finishAddAccount or finishAddAccountAsUser which may be in + // a different user profile. + private static String ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add"; + private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static void createDebugTable(SQLiteDatabase db) { @@ -4300,4 +4494,29 @@ public class AccountManagerService return mContext; } } + + private void sendResponse(IAccountManagerResponse response, Bundle result) { + try { + response.onResult(result); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote + // exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } + + private void sendErrorResponse(IAccountManagerResponse response, int errorCode, + String errorMessage) { + try { + response.onError(errorCode, errorMessage); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote + // exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } + } } diff --git a/services/core/java/com/android/server/accounts/CryptoHelper.java b/services/core/java/com/android/server/accounts/CryptoHelper.java new file mode 100644 index 000000000000..2b59b7483521 --- /dev/null +++ b/services/core/java/com/android/server/accounts/CryptoHelper.java @@ -0,0 +1,140 @@ +package com.android.server.accounts; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * A crypto helper for encrypting and decrypting bundle with in-memory symmetric + * key for {@link AccountManagerService}. + */ +/* default */ class CryptoHelper { + private static final String TAG = "Account"; + + private static final String KEY_CIPHER = "cipher"; + private static final String KEY_MAC = "mac"; + private static final String KEY_ALGORITHM = "AES"; + private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; + private static final String MAC_ALGORITHM = "HMACSHA256"; + private static final int IV_LENGTH = 16; + + private static CryptoHelper sInstance; + // Keys used for encrypting and decrypting data returned in a Bundle. + private final SecretKeySpec mCipherKeySpec; + private final SecretKeySpec mMacKeySpec; + private final IvParameterSpec mIv; + + /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException { + if (sInstance == null) { + sInstance = new CryptoHelper(); + } + return sInstance; + } + + private CryptoHelper() throws NoSuchAlgorithmException { + KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM); + SecretKey skey = kgen.generateKey(); + mCipherKeySpec = new SecretKeySpec(skey.getEncoded(), KEY_ALGORITHM); + + kgen = KeyGenerator.getInstance(MAC_ALGORITHM); + skey = kgen.generateKey(); + mMacKeySpec = new SecretKeySpec(skey.getEncoded(), MAC_ALGORITHM); + + // Create random iv + byte[] iv = new byte[IV_LENGTH]; + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(iv); + mIv = new IvParameterSpec(iv); + } + + @NonNull + /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { + Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle."); + Parcel parcel = Parcel.obtain(); + bundle.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + + Bundle encryptedBundle = new Bundle(); + + byte[] cipher = encrypt(bytes); + byte[] mac = createMac(cipher); + + encryptedBundle.putByteArray(KEY_CIPHER, cipher); + encryptedBundle.putByteArray(KEY_MAC, mac); + + return encryptedBundle; + } + + @Nullable + /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { + Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle."); + byte[] cipherArray = bundle.getByteArray(KEY_CIPHER); + byte[] macArray = bundle.getByteArray(KEY_MAC); + + if (!verifyMac(cipherArray, macArray)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Escrow mac mismatched!"); + } + return null; + } + + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, mCipherKeySpec, mIv); + byte[] decryptedBytes = cipher.doFinal(cipherArray); + + Parcel decryptedParcel = Parcel.obtain(); + decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length); + decryptedParcel.setDataPosition(0); + Bundle decryptedBundle = new Bundle(); + decryptedBundle.readFromParcel(decryptedParcel); + decryptedParcel.recycle(); + return decryptedBundle; + } + + private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] macArray) + throws GeneralSecurityException { + + if (cipherArray == null || cipherArray.length == 0 || macArray == null + || macArray.length == 0) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Cipher or MAC is empty!"); + } + return false; + } + Mac mac = Mac.getInstance(MAC_ALGORITHM); + mac.init(mMacKeySpec); + mac.update(cipherArray); + return Arrays.equals(macArray, mac.doFinal()); + } + + @NonNull + private byte[] encrypt(@NonNull byte[] data) throws GeneralSecurityException { + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, mCipherKeySpec, mIv); + return cipher.doFinal(data); + } + + @NonNull + private byte[] createMac(@NonNull byte[] cipher) throws GeneralSecurityException { + Mac mac = Mac.getInstance(MAC_ALGORITHM); + mac.init(mMacKeySpec); + return mac.doFinal(cipher); + } +} diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 4d05f9ae9da4..17b3d2a8290e 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1094,15 +1094,20 @@ public final class ActiveServices { } r = smap.mServicesByName.get(name); if (r == null && createIfNeeded) { - // Before going further -- if this app is not allowed to run in the background, - // then at this point we aren't going to let it period. - if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid, - sInfo.packageName, callingPid)) { - Slog.w(TAG, "Background execution not allowed: service " - + r.intent + " to " + name.flattenToShortString() - + " from pid=" + callingPid + " uid=" + callingUid - + " pkg=" + callingPackage); - return null; + final long token = Binder.clearCallingIdentity(); + try { + // Before going further -- if this app is not allowed to run in the + // background, then at this point we aren't going to let it period. + if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid, + sInfo.packageName, callingPid)) { + Slog.w(TAG, "Background execution not allowed: service " + + r.intent + " to " + name.flattenToShortString() + + " from pid=" + callingPid + " uid=" + callingUid + + " pkg=" + callingPackage); + return null; + } + } finally { + Binder.restoreCallingIdentity(token); } Intent.FilterComparison filter diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6e6bfb791ae7..3a0d80b6945a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16,72 +16,8 @@ package com.android.server.am; -import static android.Manifest.permission.INTERACT_ACROSS_USERS; -import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.Manifest.permission.START_TASKS_FROM_RECENTS; -import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; -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.HOME_STACK_ID; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static com.android.internal.util.XmlUtils.readBooleanAttribute; -import static com.android.internal.util.XmlUtils.readIntAttribute; -import static com.android.internal.util.XmlUtils.readLongAttribute; -import static com.android.internal.util.XmlUtils.writeBooleanAttribute; -import static com.android.internal.util.XmlUtils.writeIntAttribute; -import static com.android.internal.util.XmlUtils.writeLongAttribute; -import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; -import static com.android.server.am.ActivityManagerDebugConfig.*; -import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; -import static com.android.server.am.ActivityStackSupervisor.ON_TOP; -import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS; -import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; -import static com.android.server.am.TaskRecord.INVALID_TASK_ID; -import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; -import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; -import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; -import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; -import static org.xmlpull.v1.XmlPullParser.START_TAG; - -import android.Manifest; -import android.app.ActivityManager.StackId; -import android.app.AppOpsManager; -import android.app.ApplicationThreadNative; -import android.app.BroadcastOptions; -import android.app.IActivityContainer; -import android.app.IActivityContainerCallback; -import android.app.IAppTask; -import android.app.ITaskStackListener; -import android.app.ProfilerInfo; -import android.app.assist.AssistContent; -import android.app.assist.AssistStructure; -import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManagerInternal; -import android.appwidget.AppWidgetManager; -import android.content.pm.PermissionInfo; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.BatteryStats; -import android.os.PersistableBundle; -import android.os.PowerManager; -import android.os.ResultReceiver; -import android.os.Trace; -import android.os.TransactionTooLargeException; -import android.os.WorkSource; -import android.os.storage.IMountService; -import android.os.storage.MountServiceInternal; -import android.os.storage.StorageManager; -import android.provider.Settings.Global; -import android.service.voice.IVoiceInteractionSession; -import android.service.voice.VoiceInteractionSession; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.DebugUtils; -import android.view.Display; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -118,19 +54,16 @@ import com.android.server.pm.Installer; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; - -import libcore.io.IoUtils; -import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.Manifest; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackId; import android.app.ActivityManager.StackInfo; import android.app.ActivityManager.TaskThumbnailInfo; import android.app.ActivityManagerInternal; @@ -140,24 +73,37 @@ import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.AlertDialog; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.ApplicationErrorReport; +import android.app.ApplicationThreadNative; +import android.app.BroadcastOptions; import android.app.Dialog; +import android.app.IActivityContainer; +import android.app.IActivityContainerCallback; import android.app.IActivityController; +import android.app.IAppTask; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; import android.app.INotificationManager; import android.app.IProcessObserver; import android.app.IServiceConnection; import android.app.IStopUserCallback; -import android.app.IUidObserver; +import android.app.ITaskStackListener; import android.app.IUiAutomationConnection; +import android.app.IUidObserver; import android.app.IUserSwitchObserver; import android.app.Instrumentation; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.backup.IBackupManager; +import android.app.ProfilerInfo; import android.app.admin.DevicePolicyManager; +import android.app.assist.AssistContent; +import android.app.assist.AssistStructure; +import android.app.backup.IBackupManager; +import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManagerInternal; +import android.appwidget.AppWidgetManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ClipData; @@ -181,18 +127,24 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; -import android.content.pm.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; +import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.Rect; import android.net.Proxy; import android.net.ProxyInfo; import android.net.Uri; +import android.os.BatteryStats; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -210,21 +162,35 @@ import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; +import android.os.TransactionTooLargeException; import android.os.UpdateLock; import android.os.UserHandle; import android.os.UserManager; +import android.os.WorkSource; +import android.os.storage.IMountService; +import android.os.storage.MountServiceInternal; +import android.os.storage.StorageManager; import android.provider.Settings; +import android.service.voice.IVoiceInteractionSession; +import android.service.voice.VoiceInteractionSession; import android.text.format.DateUtils; import android.text.format.Time; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; +import android.util.DebugUtils; import android.util.EventLog; import android.util.Log; import android.util.Pair; @@ -233,13 +199,12 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.util.Xml; +import android.view.Display; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; -import dalvik.system.VMRuntime; - import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; @@ -269,6 +234,100 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import dalvik.system.VMRuntime; +import libcore.io.IoUtils; +import libcore.util.EmptyArray; + +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.START_TASKS_FROM_RECENTS; +import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; +import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; +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.HOME_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; +import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES; +import static android.provider.Settings.Global.DEBUG_APP; +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL; +import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER; +import static com.android.internal.util.XmlUtils.readBooleanAttribute; +import static com.android.internal.util.XmlUtils.readIntAttribute; +import static com.android.internal.util.XmlUtils.readLongAttribute; +import static com.android.internal.util.XmlUtils.writeBooleanAttribute; +import static com.android.internal.util.XmlUtils.writeIntAttribute; +import static com.android.internal.util.XmlUtils.writeLongAttribute; +import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CLEANUP; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IMMERSIVE; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_URI_PERMISSION; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROVIDER; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS; +import static com.android.server.am.ActivityStackSupervisor.ON_TOP; +import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS; +import static com.android.server.am.TaskRecord.INVALID_TASK_ID; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { @@ -1211,7 +1270,8 @@ public final class ActivityManagerService extends ActivityManagerNative String mOrigDebugApp = null; boolean mOrigWaitForDebugger = false; boolean mAlwaysFinishActivities = false; - boolean mForceResizableActivites; + boolean mForceResizableActivities; + boolean mSupportsFreeformWindowManagement; IActivityController mController = null; String mProfileApp = null; ProcessRecord mProfileProc = null; @@ -8827,12 +8887,19 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public Bitmap getTaskDescriptionIcon(String filename) { - if (!FileUtils.isValidExtFilename(filename) - || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) { - throw new IllegalArgumentException("Bad filename: " + filename); + public Bitmap getTaskDescriptionIcon(String filePath, int userId) { + if (userId != UserHandle.getCallingUserId()) { + enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "getTaskDescriptionIcon"); } - return mTaskPersister.getTaskDescriptionIcon(filename); + final File passedIconFile = new File(filePath); + final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId), + passedIconFile.getName()); + if (!legitIconFile.getPath().equals(filePath) + || !filePath.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) { + throw new IllegalArgumentException("Bad file path: " + filePath); + } + return mTaskPersister.getTaskDescriptionIcon(filePath); } @Override @@ -11805,21 +11872,21 @@ public final class ActivityManagerService extends ActivityManagerNative private void retrieveSettings() { final ContentResolver resolver = mContext.getContentResolver(); - String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP); - boolean waitForDebugger = Settings.Global.getInt( - resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0; - boolean alwaysFinishActivities = Settings.Global.getInt( - resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0; - boolean forceRtl = Settings.Global.getInt( - resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0; - int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0; - boolean forceResizable = Settings.Global.getInt( - resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, - defaultForceResizable) != 0; + final boolean freeformWindowManagement = + mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT); + + final String debugApp = Settings.Global.getString(resolver, DEBUG_APP); + final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0; + final boolean alwaysFinishActivities = + Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0; + final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0; + final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0; + final boolean forceResizable = Settings.Global.getInt( + resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, defaultForceResizable) != 0; // Transfer any global setting for forcing RTL layout, into a System Property - SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); + SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); - Configuration configuration = new Configuration(); + final Configuration configuration = new Configuration(); Settings.System.getConfiguration(resolver, configuration); if (forceRtl) { // This will take care of setting the correct layout direction flags @@ -11830,7 +11897,8 @@ public final class ActivityManagerService extends ActivityManagerNative mDebugApp = mOrigDebugApp = debugApp; mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger; mAlwaysFinishActivities = alwaysFinishActivities; - mForceResizableActivites = forceResizable; + mForceResizableActivities = forceResizable; + mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable; // This happens before any activities are started, so we can // change mConfiguration in-place. updateConfigurationLocked(configuration, null, true); @@ -20152,6 +20220,11 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public boolean unlockUser(int userId, byte[] token) { + return mUserController.unlockUser(userId, token); + } + + @Override public boolean switchUser(final int userId) { enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); String userName; diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index aa04bd70c714..ea8a12f81bab 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -61,6 +61,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -1212,8 +1213,10 @@ final class ActivityRecord { if (_taskDescription.getIconFilename() == null && (icon = _taskDescription.getIcon()) != null) { final String iconFilename = createImageFilename(createTime, task.taskId); - mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilename); - _taskDescription.setIconFilename(iconFilename); + final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename); + final String iconFilePath = iconFile.getAbsolutePath(); + mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilePath); + _taskDescription.setIconFilename(iconFilePath); } taskDescription = _taskDescription; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 0ec4b18ff28a..124d2ef9eab2 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -99,7 +99,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.Trace; import android.os.TransactionTooLargeException; import android.os.UserHandle; @@ -1666,9 +1665,7 @@ public final class ActivityStackSupervisor implements DisplayListener { UserInfo user = getUserInfo(userId); // TODO: Timeout for work challenge - if (user.isManagedProfile() - && mService.mContext.getSystemService(StorageManager.class) - .isPerUserEncryptionEnabled()) { + if (user.isManagedProfile() && StorageManager.isFileBasedEncryptionEnabled()) { KeyguardManager km = (KeyguardManager) mService.mContext .getSystemService(Context.KEYGUARD_SERVICE); @@ -1926,9 +1923,9 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean overrideBounds = false; Rect newBounds = null; if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) { - if (options.hasBounds()) { + if (canUseActivityOptionsLaunchBounds(options)) { overrideBounds = true; - newBounds = options.getBounds(); + newBounds = options.getLaunchBounds(); } } @@ -2930,8 +2927,8 @@ public final class ActivityStackSupervisor implements DisplayListener { } if (task.mResizeable && options != null) { - if (options.hasBounds()) { - Rect bounds = options.getBounds(); + if (canUseActivityOptionsLaunchBounds(options)) { + Rect bounds = options.getLaunchBounds(); task.updateOverrideConfiguration(bounds); final int stackId = task.getLaunchStackId(); if (stackId != task.stack.mStackId) { @@ -2955,6 +2952,12 @@ public final class ActivityStackSupervisor implements DisplayListener { "findTaskToMoveToFront: moved to front of stack=" + task.stack); } + private boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) { + // We use the launch bounds in the activity options is the device supports freeform + // window management. + return options.hasLaunchBounds() && mService.mSupportsFreeformWindowManagement; + } + ActivityStack getStack(int stackId) { return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP); } diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index 150baf0e89d5..9a0007555eb5 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -16,10 +16,11 @@ package com.android.server.am; -import android.content.pm.IPackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Debug; +import android.os.Environment; +import android.os.FileUtils; import android.os.SystemClock; import android.util.ArraySet; import android.util.AtomicFile; @@ -27,7 +28,6 @@ import android.util.Slog; import android.util.Xml; import android.os.Process; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; @@ -44,6 +44,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.List; import libcore.io.IoUtils; @@ -54,8 +55,10 @@ public class TaskPersister { /** When not flushing don't write out files faster than this */ private static final long INTER_WRITE_DELAY_MS = 500; - /** When not flushing delay this long before writing the first file out. This gives the next - * task being launched a chance to load its resources without this occupying IO bandwidth. */ + /** + * When not flushing delay this long before writing the first file out. This gives the next task + * being launched a chance to load its resources without this occupying IO bandwidth. + */ private static final long PRE_TASK_DELAY_MS = 3000; /** The maximum number of entries to keep in the queue before draining it automatically. */ @@ -72,24 +75,23 @@ public class TaskPersister { private static final String TAG_TASK = "task"; - static File sImagesDir; - static File sTasksDir; - private final ActivityManagerService mService; private final ActivityStackSupervisor mStackSupervisor; private final RecentTasks mRecentTasks; - /** Value determines write delay mode as follows: - * < 0 We are Flushing. No delays between writes until the image queue is drained and all - * tasks needing persisting are written to disk. There is no delay between writes. - * == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS. - * > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be - * delayed by #INTER_WRITE_DELAY_MS. */ + /** + * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes + * until the image queue is drained and all tasks needing persisting are written to disk. There + * is no delay between writes. == 0 We are Idle. Next writes will be delayed by + * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent + * writes will be delayed by #INTER_WRITE_DELAY_MS. + */ private long mNextWriteTime = 0; private final LazyTaskWriterThread mLazyTaskWriterThread; private static class WriteQueueItem {} + private static class TaskWriteQueueItem extends WriteQueueItem { final TaskRecord mTask; @@ -97,12 +99,13 @@ public class TaskPersister { mTask = task; } } + private static class ImageWriteQueueItem extends WriteQueueItem { - final String mFilename; + final String mFilePath; Bitmap mImage; - ImageWriteQueueItem(String filename, Bitmap image) { - mFilename = filename; + ImageWriteQueueItem(String filePath, Bitmap image) { + mFilePath = filePath; mImage = image; } } @@ -111,19 +114,18 @@ public class TaskPersister { TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, RecentTasks recentTasks) { - sTasksDir = new File(systemDir, TASKS_DIRNAME); - if (!sTasksDir.exists()) { - if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir); - if (!sTasksDir.mkdir()) { - Slog.e(TAG, "Failure creating tasks directory " + sTasksDir); + + final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME); + if (legacyImagesDir.exists()) { + if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) { + Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir); } } - sImagesDir = new File(systemDir, IMAGES_DIRNAME); - if (!sImagesDir.exists()) { - if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir); - if (!sImagesDir.mkdir()) { - Slog.e(TAG, "Failure creating images directory " + sImagesDir); + final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME); + if (legacyTasksDir.exists()) { + if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) { + Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir); } } @@ -144,8 +146,8 @@ public class TaskPersister { for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { final WriteQueueItem item = mWriteQueue.get(queueNdx); if (item instanceof ImageWriteQueueItem && - ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) { - if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename + + ((ImageWriteQueueItem) item).mFilePath.startsWith(taskString)) { + if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath + " from write queue"); mWriteQueue.remove(queueNdx); } @@ -213,14 +215,14 @@ public class TaskPersister { } } - void saveImage(Bitmap image, String filename) { + void saveImage(Bitmap image, String filePath) { synchronized (this) { int queueNdx; for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { final WriteQueueItem item = mWriteQueue.get(queueNdx); if (item instanceof ImageWriteQueueItem) { ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; - if (imageWriteQueueItem.mFilename.equals(filename)) { + if (imageWriteQueueItem.mFilePath.equals(filePath)) { // replace the Bitmap with the new one. imageWriteQueueItem.mImage = image; break; @@ -228,14 +230,14 @@ public class TaskPersister { } } if (queueNdx < 0) { - mWriteQueue.add(new ImageWriteQueueItem(filename, image)); + mWriteQueue.add(new ImageWriteQueueItem(filePath, image)); } if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { mNextWriteTime = FLUSH_QUEUE; } else if (mNextWriteTime == 0) { mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; } - if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" + + if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" + SystemClock.uptimeMillis() + " mNextWriteTime=" + mNextWriteTime + " Callers=" + Debug.getCallers(4)); notifyAll(); @@ -244,22 +246,22 @@ public class TaskPersister { yieldIfQueueTooDeep(); } - Bitmap getTaskDescriptionIcon(String filename) { + Bitmap getTaskDescriptionIcon(String filePath) { // See if it is in the write queue - final Bitmap icon = getImageFromWriteQueue(filename); + final Bitmap icon = getImageFromWriteQueue(filePath); if (icon != null) { return icon; } - return restoreImage(filename); + return restoreImage(filePath); } - Bitmap getImageFromWriteQueue(String filename) { + Bitmap getImageFromWriteQueue(String filePath) { synchronized (this) { for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { final WriteQueueItem item = mWriteQueue.get(queueNdx); if (item instanceof ImageWriteQueueItem) { ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; - if (imageWriteQueueItem.mFilename.equals(filename)) { + if (imageWriteQueueItem.mFilePath.equals(filePath)) { return imageWriteQueueItem.mImage; } } @@ -275,7 +277,7 @@ public class TaskPersister { xmlSerializer.setOutput(stringWriter); if (DEBUG) xmlSerializer.setFeature( - "http://xmlpull.org/v1/doc/features.html#indent-output", true); + "http://xmlpull.org/v1/doc/features.html#indent-output", true); // save task xmlSerializer.startDocument(null, true); @@ -321,19 +323,22 @@ public class TaskPersister { return null; } - ArrayList<TaskRecord> restoreTasksLocked(final int [] validUserIds) { - final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>(); + private List<TaskRecord> restoreTasksForUserLocked(final int userId) { + final List<TaskRecord> tasks = new ArrayList<TaskRecord>(); ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>(); - File[] recentFiles = sTasksDir.listFiles(); + File userTasksDir = getUserTasksDir(userId); + + File[] recentFiles = userTasksDir.listFiles(); if (recentFiles == null) { - Slog.e(TAG, "Unable to list files from " + sTasksDir); + Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir); return tasks; } for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) { File taskFile = recentFiles[taskNdx]; - if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName()); + if (DEBUG) Slog.d(TAG, "restoreTasksForUser: userId=" + userId + + ", taskFile=" + taskFile.getName()); BufferedReader reader = null; boolean deleteFile = false; try { @@ -348,30 +353,29 @@ public class TaskPersister { if (event == XmlPullParser.START_TAG) { if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name); if (TAG_TASK.equals(name)) { - final TaskRecord task = - TaskRecord.restoreFromXml(in, mStackSupervisor); - if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + - task); + final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor); + if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + + task); if (task != null) { // XXX Don't add to write queue... there is no reason to write // out the stuff we just read, if we don't write it we will // read the same thing again. - //mWriteQueue.add(new TaskWriteQueueItem(task)); + // mWriteQueue.add(new TaskWriteQueueItem(task)); final int taskId = task.taskId; mStackSupervisor.setNextTaskId(taskId); // Check if it's a valid user id. Don't add tasks for removed users. - if (ArrayUtils.contains(validUserIds, task.userId)) { + if (userId == task.userId) { task.isPersistable = true; tasks.add(task); recoveredTaskIds.add(taskId); } } else { - Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " + - fileToString(taskFile)); + Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile=" + + taskFile + ": " + fileToString(taskFile)); } } else { - Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event + - " name=" + name); + Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event + + " name=" + name); } } XmlUtils.skipCurrentTag(in); @@ -390,10 +394,19 @@ public class TaskPersister { } if (!DEBUG) { - removeObsoleteFiles(recoveredTaskIds); + removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles()); } + return tasks; + } + + ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) { + final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>(); - // Fixup task affiliation from taskIds + for (int userId : validUserIds) { + tasks.addAll(restoreTasksForUserLocked(userId)); + } + + // Fix up task affiliation from taskIds for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = tasks.get(taskNdx); task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks)); @@ -420,7 +433,7 @@ public class TaskPersister { } private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { - if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds + + if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds + " files=" + files); if (files == null) { Slog.e(TAG, "File error accessing recents directory (too many files open?)."); @@ -434,14 +447,14 @@ public class TaskPersister { final int taskId; try { taskId = Integer.valueOf(filename.substring(0, taskIdEnd)); - if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId); + if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId); } catch (Exception e) { - Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName()); + Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName()); file.delete(); continue; } if (!persistentTaskIds.contains(taskId)) { - if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName()); + if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName()); file.delete(); } } @@ -449,13 +462,39 @@ public class TaskPersister { } private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) { - removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles()); - removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles()); + for (int userId : mService.getRunningUserIds()) { + removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles()); + removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles()); + } } static Bitmap restoreImage(String filename) { if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename); - return BitmapFactory.decodeFile(sImagesDir + File.separator + filename); + return BitmapFactory.decodeFile(filename); + } + + static File getUserTasksDir(int userId) { + File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME); + + if (!userTasksDir.exists()) { + if (!userTasksDir.mkdir()) { + Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": " + + userTasksDir); + } + } + return userTasksDir; + } + + static File getUserImagesDir(int userId) { + File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME); + + if (!userImagesDir.exists()) { + if (!userImagesDir.mkdir()) { + Slog.e(TAG, "Failure creating images directory for user " + userId + ": " + + userImagesDir); + } + } + return userImagesDir; } private class LazyTaskWriterThread extends Thread { @@ -508,7 +547,6 @@ public class TaskPersister { INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")"); } - while (mWriteQueue.isEmpty()) { if (mNextWriteTime != 0) { mNextWriteTime = 0; // idle. @@ -542,15 +580,15 @@ public class TaskPersister { if (item instanceof ImageWriteQueueItem) { ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; - final String filename = imageWriteQueueItem.mFilename; + final String filePath = imageWriteQueueItem.mFilePath; final Bitmap bitmap = imageWriteQueueItem.mImage; - if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename); + if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath); FileOutputStream imageFile = null; try { - imageFile = new FileOutputStream(new File(sImagesDir, filename)); + imageFile = new FileOutputStream(new File(filePath)); bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile); } catch (Exception e) { - Slog.e(TAG, "saveImage: unable to save " + filename, e); + Slog.e(TAG, "saveImage: unable to save " + filePath, e); } finally { IoUtils.closeQuietly(imageFile); } @@ -575,18 +613,21 @@ public class TaskPersister { FileOutputStream file = null; AtomicFile atomicFile = null; try { - atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf( - task.taskId) + RECENTS_FILENAME + TASK_EXTENSION)); + atomicFile = new AtomicFile(new File( + getUserTasksDir(task.userId), + String.valueOf(task.taskId) + RECENTS_FILENAME + + TASK_EXTENSION)); file = atomicFile.startWrite(); file.write(stringWriter.toString().getBytes()); file.write('\n'); atomicFile.finishWrite(file); + } catch (IOException e) { if (file != null) { atomicFile.failWrite(file); } - Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + - e); + Slog.e(TAG, + "Unable to open " + atomicFile + " for persisting. " + e); } } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index b2140806b4aa..3fc6846b812f 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -27,15 +27,23 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; -import static com.android.server.am.ActivityManagerDebugConfig.*; -import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_ADD_REMOVE; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; +import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.StackId; -import android.app.ActivityManager.TaskThumbnail; import android.app.ActivityManager.TaskDescription; import android.app.ActivityManager.TaskThumbnail; import android.app.ActivityManager.TaskThumbnailInfo; @@ -58,8 +66,10 @@ import android.os.UserHandle; import android.service.voice.IVoiceInteractionSession; import android.util.DisplayMetrics; import android.util.Slog; + import com.android.internal.app.IVoiceInteractor; import com.android.internal.util.XmlUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -237,7 +247,8 @@ final class TaskRecord { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; - mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename); + userId = UserHandle.getUserId(info.applicationInfo.uid); + mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename); mLastThumbnailInfo = new TaskThumbnailInfo(); taskId = _taskId; mAffiliatedTaskId = _taskId; @@ -256,7 +267,8 @@ final class TaskRecord { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; - mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename); + userId = UserHandle.getUserId(info.applicationInfo.uid); + mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename); mLastThumbnailInfo = thumbnailInfo; taskId = _taskId; mAffiliatedTaskId = _taskId; @@ -276,7 +288,6 @@ final class TaskRecord { taskType = APPLICATION_ACTIVITY_TYPE; mTaskToReturnTo = HOME_ACTIVITY_TYPE; - userId = UserHandle.getUserId(info.applicationInfo.uid); lastTaskDescription = _taskDescription; mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1; } @@ -294,7 +305,7 @@ final class TaskRecord { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; - mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename); + mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(_userId), mFilename); mLastThumbnailInfo = lastThumbnailInfo; taskId = _taskId; intent = _intent; @@ -326,7 +337,7 @@ final class TaskRecord { mNextAffiliateTaskId = nextTaskId; mCallingUid = callingUid; mCallingPackage = callingPackage; - mResizeable = resizeable || mService.mForceResizableActivites; + mResizeable = resizeable || mService.mForceResizableActivities; mPrivileged = privileged; ActivityInfo info = (mActivities.size() > 0) ? mActivities.get(0).info : null; mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1; @@ -428,7 +439,7 @@ final class TaskRecord { } else { autoRemoveRecents = false; } - mResizeable = info.resizeable || mService.mForceResizableActivites; + mResizeable = info.resizeable || mService.mForceResizableActivities; mLockTaskMode = info.lockTaskLaunchMode; mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0; setLockTaskAuth(); @@ -537,7 +548,7 @@ final class TaskRecord { mLastThumbnailFile.delete(); } } else { - mService.mTaskPersister.saveImage(thumbnail, mFilename); + mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath()); } return true; } @@ -549,7 +560,8 @@ final class TaskRecord { thumbs.thumbnailInfo = mLastThumbnailInfo; thumbs.thumbnailFileDescriptor = null; if (mLastThumbnail == null) { - thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(mFilename); + thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue( + mLastThumbnailFile.getAbsolutePath()); } // Only load the thumbnail file if we don't have a thumbnail if (thumbs.mainThumbnail == null && mLastThumbnailFile.exists()) { @@ -682,7 +694,7 @@ final class TaskRecord { // Only set this based on the first activity if (mActivities.isEmpty()) { taskType = r.mActivityType; - if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) { + if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivities) { mResizeable = r.info.resizeable; } isPersistable = r.isPersistable(); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d6fced6a2b9a..e04f138fb421 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -66,6 +66,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.pm.UserManagerService; @@ -99,7 +100,9 @@ final class UserController { /** * Which users have been started, so are allowed to run code. */ + @GuardedBy("mService") private final SparseArray<UserState> mStartedUsers = new SparseArray<>(); + /** * LRU list of history of current users. Most recently current is at the end. */ @@ -415,7 +418,7 @@ final class UserController { private void updateUserUnlockedState(UserState uss) { final IMountService mountService = IMountService.Stub - .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE)); + .asInterface(ServiceManager.getService("mount")); if (mountService != null) { try { uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier()); @@ -424,7 +427,7 @@ final class UserController { } } else { // System isn't fully booted yet, so guess based on property - uss.unlocked = !SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false); + uss.unlocked = !StorageManager.isFileBasedEncryptionEnabled(); } } @@ -606,6 +609,35 @@ final class UserController { return result; } + boolean unlockUser(final int userId, byte[] token) { + if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: unlockUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + final UserInfo userInfo = getUserInfo(userId); + final IMountService mountService = IMountService.Stub + .asInterface(ServiceManager.getService("mount")); + try { + mountService.unlockUserKey(userId, userInfo.serialNumber, token); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to unlock: " + e.getMessage()); + throw e.rethrowAsRuntimeException(); + } + + synchronized (mService) { + final UserState uss = mStartedUsers.get(userId); + updateUserUnlockedState(uss); + } + + return true; + } + void showUserSwitchDialog(int userId, String userName) { // The dialog will show and then initiate the user switch by calling startUserInForeground Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index fd1e9dd1f465..4424838bf612 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1224,19 +1224,6 @@ public class NotificationManagerService extends SystemService { } @Override - public void setPackagePeekable(String pkg, int uid, boolean peekable) { - checkCallerIsSystem(); - - mRankingHelper.setPackagePeekable(pkg, uid, peekable); - } - - @Override - public boolean getPackagePeekable(String pkg, int uid) { - checkCallerIsSystem(); - return mRankingHelper.getPackagePeekable(pkg, uid); - } - - @Override public void setPackageVisibilityOverride(String pkg, int uid, int visibility) { checkCallerIsSystem(); mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility); @@ -2157,14 +2144,6 @@ public class NotificationManagerService extends SystemService { notification.priority = Notification.PRIORITY_HIGH; } } - // force no heads up per package config - if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) { - if (notification.extras == null) { - notification.extras = new Bundle(); - } - notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP, - Notification.HEADS_UP_NEVER); - } // 1. initial score: buckets of 10, around the app [-20..20] final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 803db10cd03f..aea137bc7923 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -20,10 +20,6 @@ public interface RankingConfig { void setPackagePriority(String packageName, int uid, int priority); - boolean getPackagePeekable(String packageName, int uid); - - void setPackagePeekable(String packageName, int uid, boolean peekable); - int getPackageVisibilityOverride(String packageName, int uid); void setPackageVisibilityOverride(String packageName, int uid, int visibility); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 66381f5ac09a..f8b661fc5365 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -49,11 +49,9 @@ public class RankingHelper implements RankingConfig { private static final String ATT_NAME = "name"; private static final String ATT_UID = "uid"; private static final String ATT_PRIORITY = "priority"; - private static final String ATT_PEEKABLE = "peekable"; private static final String ATT_VISIBILITY = "visibility"; private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; - private static final boolean DEFAULT_PEEKABLE = true; private static final int DEFAULT_VISIBILITY = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; @@ -141,7 +139,6 @@ public class RankingHelper implements RankingConfig { if (TAG_PACKAGE.equals(tag)) { int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID); int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY); - boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE); int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY); String name = parser.getAttributeValue(null, ATT_NAME); @@ -167,9 +164,6 @@ public class RankingHelper implements RankingConfig { if (priority != DEFAULT_PRIORITY) { r.priority = priority; } - if (peekable != DEFAULT_PEEKABLE) { - r.peekable = peekable; - } if (vis != DEFAULT_VISIBILITY) { r.visibility = vis; } @@ -200,8 +194,7 @@ public class RankingHelper implements RankingConfig { final int N = mRecords.size(); for (int i = N - 1; i >= 0; i--) { final Record r = mRecords.valueAt(i); - if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE - && r.visibility == DEFAULT_VISIBILITY) { + if (r.priority == DEFAULT_PRIORITY && r.visibility == DEFAULT_VISIBILITY) { mRecords.remove(i); } } @@ -223,9 +216,6 @@ public class RankingHelper implements RankingConfig { if (r.priority != DEFAULT_PRIORITY) { out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority)); } - if (r.peekable != DEFAULT_PEEKABLE) { - out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable)); - } if (r.visibility != DEFAULT_VISIBILITY) { out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); } @@ -348,22 +338,6 @@ public class RankingHelper implements RankingConfig { } @Override - public boolean getPackagePeekable(String packageName, int uid) { - final Record r = mRecords.get(recordKey(packageName, uid)); - return r != null ? r.peekable : DEFAULT_PEEKABLE; - } - - @Override - public void setPackagePeekable(String packageName, int uid, boolean peekable) { - if (peekable == getPackagePeekable(packageName, uid)) { - return; - } - getOrCreateRecord(packageName, uid).peekable = peekable; - removeDefaultRecords(); - updateConfig(); - } - - @Override public int getPackageVisibilityOverride(String packageName, int uid) { final Record r = mRecords.get(recordKey(packageName, uid)); return r != null ? r.visibility : DEFAULT_VISIBILITY; @@ -415,10 +389,6 @@ public class RankingHelper implements RankingConfig { pw.print(" priority="); pw.print(Notification.priorityToString(r.priority)); } - if (r.peekable != DEFAULT_PEEKABLE) { - pw.print(" peekable="); - pw.print(r.peekable); - } if (r.visibility != DEFAULT_VISIBILITY) { pw.print(" visibility="); pw.print(Notification.visibilityToString(r.visibility)); @@ -460,7 +430,6 @@ public class RankingHelper implements RankingConfig { String pkg; int uid = UNKNOWN_UID; int priority = DEFAULT_PRIORITY; - boolean peekable = DEFAULT_PEEKABLE; int visibility = DEFAULT_VISIBILITY; } diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 04202696f78b..c9e1315481a3 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -41,6 +41,9 @@ import java.util.Map; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import android.os.SystemClock; +import com.android.internal.logging.MetricsLogger; + /** * This {@link NotificationSignalExtractor} attempts to validate * people references. Also elevates the priority of real people. @@ -218,6 +221,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras, float[] affinityOut) { + long start = SystemClock.elapsedRealtime(); float affinity = NONE; if (extras == null) { return null; @@ -251,6 +255,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { // record the best available data, so far: affinityOut[0] = affinity; + MetricsLogger.histogram(mBaseContext, "validate_people_cache_latency", + (int) (SystemClock.elapsedRealtime() - start)); + if (pendingLookups.isEmpty()) { if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity); return null; @@ -430,6 +437,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { @Override public void work() { + long start = SystemClock.elapsedRealtime(); if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey); long timeStartMs = System.currentTimeMillis(); for (final String handle: mPendingLookups) { @@ -468,6 +476,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE, mContactAffinity == STARRED_CONTACT, false /* cached */); } + + MetricsLogger.histogram(mBaseContext, "validate_people_lookup_latency", + (int) (SystemClock.elapsedRealtime() - start)); } @Override diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 8fac9dac4a64..073b4f03da99 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -57,6 +57,8 @@ final class DefaultPermissionGrantPolicy { private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars private static final boolean DEBUG = false; + private static final int DEFAULT_FLAGS = PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS; + private static final String AUDIO_MIME_TYPE = "audio/mpeg"; private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>(); @@ -696,7 +698,7 @@ final class DefaultPermissionGrantPolicy { private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr( Intent intent, int userId) { ResolveInfo handler = mService.resolveIntent(intent, - intent.resolveType(mService.mContext.getContentResolver()), 0, userId); + intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId); if (handler == null || handler.activityInfo == null) { return null; } @@ -711,7 +713,7 @@ final class DefaultPermissionGrantPolicy { private PackageParser.Package getDefaultSystemHandlerServicePackageLPr( Intent intent, int userId) { List<ResolveInfo> handlers = mService.queryIntentServices(intent, - intent.resolveType(mService.mContext.getContentResolver()), 0, userId); + intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId); if (handlers == null) { return null; } @@ -738,7 +740,8 @@ final class DefaultPermissionGrantPolicy { homeIntent.setPackage(syncAdapterPackageName); ResolveInfo homeActivity = mService.resolveIntent(homeIntent, - homeIntent.resolveType(mService.mContext.getContentResolver()), 0, userId); + homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, + userId); if (homeActivity != null) { continue; } @@ -754,7 +757,7 @@ final class DefaultPermissionGrantPolicy { private PackageParser.Package getDefaultProviderAuthorityPackageLPr( String authority, int userId) { - ProviderInfo provider = mService.resolveContentProvider(authority, 0, userId); + ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId); if (provider != null) { return getSystemPackageLPr(provider.packageName); } diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java new file mode 100644 index 000000000000..628ad0ebea41 --- /dev/null +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -0,0 +1,187 @@ +/* + * 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.TimedRemoteCaller; + +import com.android.internal.app.EphemeralResolverService; +import com.android.internal.app.EphemeralResolveInfo; +import com.android.internal.app.IEphemeralResolver; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +/** + * Represents a remote ephemeral resolver. It is responsible for binding to the remote + * service and handling all interactions in a timely manner. + * @hide + */ +final class EphemeralResolverConnection { + // This is running in a critical section and the timeout must be sufficiently low + private static final long BIND_SERVICE_TIMEOUT_MS = + ("eng".equals(Build.TYPE)) ? 300 : 200; + + private final Object mLock = new Object(); + private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller = + new GetEphemeralResolveInfoCaller(); + private final ServiceConnection mServiceConnection = new MyServiceConnection(); + private final Context mContext; + /** Intent used to bind to the service */ + private final Intent mIntent; + + private IEphemeralResolver mRemoteInstance; + + public EphemeralResolverConnection(Context context, ComponentName componentName) { + mContext = context; + mIntent = new Intent().setComponent(componentName); + } + + public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) { + throwIfCalledOnMainThread(); + try { + return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList( + getRemoteInstanceLazy(), hashPrefix); + } catch (RemoteException re) { + } catch (TimeoutException te) { + } finally { + synchronized (mLock) { + mLock.notifyAll(); + } + } + return null; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.append(prefix).append("bound=") + .append((mRemoteInstance != null) ? "true" : "false").println(); + + pw.flush(); + + try { + getRemoteInstanceLazy().asBinder().dump(fd, new String[] { prefix }); + } catch (TimeoutException te) { + /* ignore */ + } catch (RemoteException re) { + /* ignore */ + } + } + } + + private IEphemeralResolver getRemoteInstanceLazy() throws TimeoutException { + synchronized (mLock) { + if (mRemoteInstance != null) { + return mRemoteInstance; + } + bindLocked(); + return mRemoteInstance; + } + } + + private void bindLocked() throws TimeoutException { + if (mRemoteInstance != null) { + return; + } + + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.SYSTEM); + + final long startMillis = SystemClock.uptimeMillis(); + while (true) { + if (mRemoteInstance != null) { + break; + } + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis; + if (remainingMillis <= 0) { + throw new TimeoutException("Didn't bind to resolver in time."); + } + try { + mLock.wait(remainingMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + + mLock.notifyAll(); + } + + private void throwIfCalledOnMainThread() { + if (Thread.currentThread() == mContext.getMainLooper().getThread()) { + throw new RuntimeException("Cannot invoke on the main thread"); + } + } + + private final class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mRemoteInstance = IEphemeralResolver.Stub.asInterface(service); + mLock.notifyAll(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + mRemoteInstance = null; + } + } + } + + private static final class GetEphemeralResolveInfoCaller + extends TimedRemoteCaller<List<EphemeralResolveInfo>> { + private final IRemoteCallback mCallback; + + public GetEphemeralResolveInfoCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) throws RemoteException { + final ArrayList<EphemeralResolveInfo> resolveList = + data.getParcelableArrayList( + EphemeralResolverService.EXTRA_RESOLVE_INFO); + int sequence = + data.getInt(EphemeralResolverService.EXTRA_SEQUENCE, -1); + onRemoteMethodResult(resolveList, sequence); + } + }; + } + + public List<EphemeralResolveInfo> getEphemeralResolveInfoList( + IEphemeralResolver target, int hashPrefix) + throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence); + return getResultTimed(sequence); + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 992919e7173f..4f3544b3e380 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -209,6 +209,7 @@ import libcore.util.EmptyArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.EphemeralResolveInfo; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; @@ -252,6 +253,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateEncodingException; @@ -301,6 +303,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_VERIFY = false; private static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; + private static final boolean DEBUG_EPHEMERAL = false; static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false; @@ -598,6 +601,16 @@ public class PackageManagerService extends IPackageManager.Stub { private final ComponentName mIntentFilterVerifierComponent; private int mIntentFilterVerificationToken = 0; + /** Component that knows whether or not an ephemeral application exists */ + final ComponentName mEphemeralResolverComponent; + /** The service connection to the ephemeral resolver */ + final EphemeralResolverConnection mEphemeralResolverConnection; + + /** Component used to install ephemeral applications */ + final ComponentName mEphemeralInstallerComponent; + final ActivityInfo mEphemeralInstallerActivity = new ActivityInfo(); + final ResolveInfo mEphemeralInstallerInfo = new ResolveInfo(); + final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates = new SparseArray<IntentFilterVerificationState>(); @@ -2346,6 +2359,33 @@ public class PackageManagerService extends IPackageManager.Stub { mIntentFilterVerifier = new IntentVerifierProxy(mContext, mIntentFilterVerifierComponent); + final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr(); + final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr(); + // both the installer and resolver must be present to enable ephemeral + if (ephemeralInstallerComponent != null && ephemeralResolverComponent != null) { + if (DEBUG_EPHEMERAL) { + Slog.i(TAG, "Ephemeral activated; resolver: " + ephemeralResolverComponent + + " installer:" + ephemeralInstallerComponent); + } + mEphemeralResolverComponent = ephemeralResolverComponent; + mEphemeralInstallerComponent = ephemeralInstallerComponent; + setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent); + mEphemeralResolverConnection = + new EphemeralResolverConnection(mContext, mEphemeralResolverComponent); + } else { + if (DEBUG_EPHEMERAL) { + final String missingComponent = + (ephemeralResolverComponent == null) + ? (ephemeralInstallerComponent == null) + ? "resolver and installer" + : "resolver" + : "installer"; + Slog.i(TAG, "Ephemeral deactivated; missing " + missingComponent); + } + mEphemeralResolverComponent = null; + mEphemeralInstallerComponent = null; + mEphemeralResolverConnection = null; + } } // synchronized (mPackages) } // synchronized (mInstallLock) @@ -2484,6 +2524,89 @@ public class PackageManagerService extends IPackageManager.Stub { return verifierComponentName; } + private ComponentName getEphemeralResolverLPr() { + final String[] packageArray = + mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); + if (packageArray.length == 0) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver NOT found; empty package list"); + } + return null; + } + + Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE); + final List<ResolveInfo> resolvers = queryIntentServices(resolverIntent, + null /*resolvedType*/, 0 /*flags*/, UserHandle.USER_SYSTEM); + + final int N = resolvers.size(); + if (N == 0) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters"); + } + return null; + } + + final Set<String> possiblePackages = new ArraySet<>(Arrays.asList(packageArray)); + for (int i = 0; i < N; i++) { + final ResolveInfo info = resolvers.get(i); + + if (info.serviceInfo == null) { + continue; + } + + final String packageName = info.serviceInfo.packageName; + if (!possiblePackages.contains(packageName)) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver not in allowed package list;" + + " pkg: " + packageName + ", info:" + info); + } + continue; + } + + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Ephemeral resolver found;" + + " pkg: " + packageName + ", info:" + info); + } + return new ComponentName(packageName, info.serviceInfo.name); + } + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Ephemeral resolver NOT found"); + } + return null; + } + + private ComponentName getEphemeralInstallerLPr() { + Intent installerIntent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE); + installerIntent.addCategory(Intent.CATEGORY_DEFAULT); + installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); + final List<ResolveInfo> installers = queryIntentActivities(installerIntent, + PACKAGE_MIME_TYPE, 0 /*flags*/, 0 /*userId*/); + + ComponentName ephemeralInstaller = null; + + final int N = installers.size(); + for (int i = 0; i < N; i++) { + final ResolveInfo info = installers.get(i); + final String packageName = info.activityInfo.packageName; + + if (!info.activityInfo.applicationInfo.isSystemApp()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral installer is not system app;" + + " pkg: " + packageName + ", info:" + info); + } + continue; + } + + if (ephemeralInstaller != null) { + throw new RuntimeException("There must only be one ephemeral installer"); + } + + ephemeralInstaller = new ComponentName(packageName, info.activityInfo.name); + } + + return ephemeralInstaller; + } + private void primeDomainVerificationsLPw(int userId) { if (DEBUG_DOMAIN_VERIFICATION) { Slog.d(TAG, "Priming domain verifications in user " + userId); @@ -3021,18 +3144,19 @@ public class PackageManagerService extends IPackageManager.Stub { * purposefully done before acquiring {@link #mPackages} lock. */ private int augmentFlagsForUser(int flags, int userId) { - if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) { + if (StorageManager.isFileBasedEncryptionEnabled()) { final IMountService mount = IMountService.Stub - .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE)); + .asInterface(ServiceManager.getService("mount")); if (mount == null) { // We must be early in boot, so the best we can do is assume the // user is fully running. + Slog.w(TAG, "Early during boot, assuming not encrypted"); return flags; } final long token = Binder.clearCallingIdentity(); try { if (!mount.isUserKeyUnlocked(userId)) { - flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA; + flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY; } } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -4270,8 +4394,97 @@ public class PackageManagerService extends IPackageManager.Stub { false, false, false, userId); } + private boolean isEphemeralAvailable(Intent intent, String resolvedType, int userId) { + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + // If we can't create a digest, ignore ephemeral apps. + return false; + } + + final byte[] hostBytes = intent.getData().getHost().getBytes(); + final byte[] digestBytes = digest.digest(hostBytes); + int shaPrefix = + digestBytes[0] << 24 + | digestBytes[1] << 16 + | digestBytes[2] << 8 + | digestBytes[3] << 0; + final List<EphemeralResolveInfo> ephemeralResolveInfoList = + mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix); + if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) { + // No hash prefix match; there are no ephemeral apps for this domain. + return false; + } + for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) { + EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i); + if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) { + continue; + } + final List<IntentFilter> filters = ephemeralApplication.getFilters(); + // No filters; this should never happen. + if (filters.isEmpty()) { + continue; + } + // We have a domain match; resolve the filters to see if anything matches. + final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver(); + for (int j = filters.size() - 1; j >= 0; --j) { + ephemeralResolver.addFilter(filters.get(j)); + } + List<ResolveInfo> ephemeralResolveList = ephemeralResolver.queryIntent( + intent, resolvedType, false /*defaultOnly*/, userId); + return !ephemeralResolveList.isEmpty(); + } + // Hash or filter mis-match; no ephemeral apps for this domain. + return false; + } + private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, int userId) { + final boolean isWebUri = hasWebURI(intent); + // Check whether or not an ephemeral app exists to handle the URI. + if (isWebUri && mEphemeralResolverConnection != null) { + // Deny ephemeral apps if the user choose _ALWAYS or _ALWAYS_ASK for intent resolution. + boolean hasAlwaysHandler = false; + synchronized (mPackages) { + final int count = query.size(); + for (int n=0; n<count; n++) { + ResolveInfo info = query.get(n); + String packageName = info.activityInfo.packageName; + PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + // Try to get the status from User settings first + long packedStatus = getDomainVerificationStatusLPr(ps, userId); + int status = (int) (packedStatus >> 32); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS + || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { + hasAlwaysHandler = true; + break; + } + } + } + } + + // Only consider installing an ephemeral app if there isn't already a verified handler. + // We've determined that there's an ephemeral app available for the URI, ignore any + // ResolveInfo's and just return the ephemeral installer + if (!hasAlwaysHandler && isEphemeralAvailable(intent, resolvedType, userId)) { + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Resolving to the ephemeral installer"); + } + // ditch the result and return a ResolveInfo to launch the ephemeral installer + ResolveInfo ri = new ResolveInfo(mEphemeralInstallerInfo); + ri.activityInfo = new ActivityInfo(ri.activityInfo); + // make a deep copy of the applicationInfo + ri.activityInfo.applicationInfo = new ApplicationInfo( + ri.activityInfo.applicationInfo); + if (userId != 0) { + ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId, + UserHandle.getAppId(ri.activityInfo.applicationInfo.uid)); + } + return ri; + } + } if (query != null) { final int N = query.size(); if (N == 1) { @@ -6302,22 +6515,25 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) { - int[] users = sUserManager.getUserIds(); + private void createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) + throws PackageManagerException { int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo); - if (res < 0) { - return res; + if (res != 0) { + throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, + "Failed to install " + packageName + ": " + res); } + + final int[] users = sUserManager.getUserIds(); for (int user : users) { if (user != 0) { res = mInstaller.createUserData(volumeUuid, packageName, UserHandle.getUid(user, uid), user, seinfo); - if (res < 0) { - return res; + if (res != 0) { + throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, + "Failed to createUserData " + packageName + ": " + res); } } } - return res; } private int removeDataDirsLI(String volumeUuid, String packageName) { @@ -6887,18 +7103,6 @@ public class PackageManagerService extends IPackageManager.Stub { + pkg.applicationInfo.uid + "; old data erased"; reportSettingsProblem(Log.WARN, msg); recovered = true; - - // And now re-install the app. - ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid, - pkg.applicationInfo.seinfo); - if (ret == -1) { - // Ack should not happen! - msg = prefix + pkg.packageName - + " could not have data directory re-created after delete."; - reportSettingsProblem(Log.WARN, msg); - throw new PackageManagerException( - INSTALL_FAILED_INSUFFICIENT_STORAGE, msg); - } } if (!recovered) { mHasSystemUidErrors = true; @@ -6931,6 +7135,10 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // Ensure that directories are prepared + createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid, + pkg.applicationInfo.seinfo); + if (mShouldRestoreconData) { Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued."); mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName, @@ -6941,14 +7149,8 @@ public class PackageManagerService extends IPackageManager.Stub { if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) Log.v(TAG, "Want this data dir: " + dataPath); } - //invoke installer to do the actual installation - int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid, + createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.seinfo); - if (ret < 0) { - // Error from installer - throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, - "Unable to create data dirs [errorCode=" + ret + "]"); - } } // Get all of our default paths setup @@ -7780,6 +7982,30 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void setUpEphemeralInstallerActivityLP(ComponentName installerComponent) { + final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName()); + + // Set up information for ephemeral installer activity + mEphemeralInstallerActivity.applicationInfo = pkg.applicationInfo; + mEphemeralInstallerActivity.name = mEphemeralInstallerComponent.getClassName(); + mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName; + mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName; + mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; + mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | + ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; + mEphemeralInstallerActivity.theme = 0; + mEphemeralInstallerActivity.exported = true; + mEphemeralInstallerActivity.enabled = true; + mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity; + mEphemeralInstallerInfo.priority = 0; + mEphemeralInstallerInfo.preferredOrder = 0; + mEphemeralInstallerInfo.match = 0; + + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent); + } + } + private static String calculateBundledApkRoot(final String codePathString) { final File codePath = new File(codePathString); final File codeRoot; @@ -9336,7 +9562,28 @@ public class PackageManagerService extends IPackageManager.Stub { private final ArrayMap<ComponentName, PackageParser.Provider> mProviders = new ArrayMap<ComponentName, PackageParser.Provider>(); private int mFlags; - }; + } + + private static final class EphemeralIntentResolver + extends IntentResolver<IntentFilter, ResolveInfo> { + @Override + protected IntentFilter[] newArray(int size) { + return new IntentFilter[size]; + } + + @Override + protected boolean isPackageForFilter(String packageName, IntentFilter info) { + return true; + } + + @Override + protected ResolveInfo newResult(IntentFilter info, int match, int userId) { + if (!sUserManager.exists(userId)) return null; + final ResolveInfo res = new ResolveInfo(); + res.filter = info; + return res; + } + } private static final Comparator<ResolveInfo> mResolvePrioritySorter = new Comparator<ResolveInfo>() { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 2cedc9ccd7c1..dbb58185e447 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -1000,7 +1000,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" the text in FILTER."); pw.println(" Options:"); pw.println(" -f: see their associated file"); - pw.println(" -d: filter to only show disbled packages"); + pw.println(" -d: filter to only show disabled packages"); pw.println(" -e: filter to only show enabled packages"); pw.println(" -s: filter to only show system packages"); pw.println(" -3: filter to only show third party packages"); @@ -1055,4 +1055,3 @@ class PackageManagerShellCommand extends ShellCommand { } } } - diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 1d299d74b2db..99aa30bed5a9 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3802,8 +3802,7 @@ final class Settings { if ((flags & PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS) != 0) { return true; } - if ((flags & PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA) != 0) { - // When running with amnesia, we can only run encryption-aware apps + if ((flags & PackageManager.MATCH_ENCRYPTION_AWARE_ONLY) != 0) { return componentInfo.encryptionAware; } return true; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index ab0b182762e1..baeccb44e040 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -322,7 +322,7 @@ public class UserManagerService extends IUserManager.Stub { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { UserInfo ui = mUsers.valueAt(i); - if (ui.isPrimary()) { + if (ui.isPrimary() && !mRemovingUserIds.get(ui.id)) { return ui; } } @@ -392,7 +392,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public int getCredentialOwnerProfile(int userHandle) { checkManageUsersPermission("get the credential owner"); - if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) { + if (!StorageManager.isFileBasedEncryptionEnabled()) { synchronized (mUsersLock) { UserInfo profileParent = getProfileParentLU(userHandle); if (profileParent != null) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 121ef21812a1..ae6874f720b1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5318,11 +5318,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private boolean shouldDispatchInputWhenNonInteractive() { - if (mDisplay == null || mDisplay.getState() == Display.STATE_OFF) { - return false; - } - // Send events to keyguard while the screen is on and it's showing. - if (isKeyguardShowingAndNotOccluded()) { + // Send events to keyguard while the screen is on. + if (isKeyguardShowingAndNotOccluded() && mDisplay != null + && mDisplay.getState() != Display.STATE_OFF) { return true; } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 943c9ed39262..8b1a8305d5b1 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -1480,7 +1480,9 @@ public class AppTransition implements Dump { mNextAppTransitionFutureCallback, null /* finishedCallback */, mNextAppTransitionScaleUp); mNextAppTransitionFutureCallback = null; - mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp); + if (specs != null) { + mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp); + } } mService.requestTraversal(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3ad2610a88b5..253fdad18dce 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -55,6 +55,7 @@ import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START import android.Manifest; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManagerNative; import android.app.AppOpsManager; @@ -3700,14 +3701,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) { + void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) { // This is used by freeform <-> recents windows transition. We need to synchronize // the animation with the appearance of the content of recents, so we will make // animation stay on the first or last frame a little longer. - if (specs == null) { - Slog.wtf(TAG, "prolongAnimationsFromSpecs: AppTransitionAnimationSpec is null!"); - return; - } mTmpTaskIds.clear(); for (int i = specs.length - 1; i >= 0; i--) { mTmpTaskIds.put(specs[i].taskId, 0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 31c367001e9b..844cca562786 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3852,7 +3852,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } enforceCrossUserPermission(userHandle); // Managed Profile password can only be changed when per user encryption is present. - if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) { + if (!StorageManager.isFileBasedEncryptionEnabled()) { enforceNotManagedProfile(userHandle, "set the active password"); } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index e0f95cfa18a0..c734fabe2624 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -772,7 +772,8 @@ public class UsbDeviceManager { } private void updateUsbNotification() { - if (mNotificationManager == null || !mUseUsbNotification) return; + if (mNotificationManager == null || !mUseUsbNotification + || ("0".equals(SystemProperties.get("persist.charging.notify")))) return; int id = 0; Resources r = mContext.getResources(); if (mConnected || mHostConnected) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 1ec054720547..4436a4076078 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -167,6 +167,11 @@ public final class BridgeWindowSession implements IWindowSession { } @Override + public void cancelDrag(IBinder dragToken) { + // pass for now + } + + @Override public void dragRecipientEntered(IWindow window) throws RemoteException { // pass for now } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index ae4a57d8eea4..7ef75662aad4 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -17,6 +17,7 @@ package com.android.tools.layoutlib.create; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -40,6 +41,7 @@ public class DelegateClassAdapter extends ClassVisitor { private final String mClassName; private final Set<String> mDelegateMethods; private final Log mLog; + private boolean mIsStaticInnerClass; /** * Creates a new {@link DelegateClassAdapter} that can transform some methods @@ -62,16 +64,30 @@ public class DelegateClassAdapter extends ClassVisitor { mLog = log; mClassName = className; mDelegateMethods = delegateMethods; + // If this is an inner class, by default, we assume it's static. If it's not we will detect + // by looking at the fields (see visitField) + mIsStaticInnerClass = className.contains("$"); } //---------------------------------- // Methods from the ClassAdapter @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + if (mIsStaticInnerClass && "this$0".equals(name)) { + // Having a "this$0" field, proves that this class is not a static inner class. + mIsStaticInnerClass = false; + } + + return super.visitField(access, name, desc, signature, value); + } + + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0; boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || @@ -96,7 +112,8 @@ public class DelegateClassAdapter extends ClassVisitor { MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions); DelegateMethodAdapter a = new DelegateMethodAdapter( - mLog, null, mwDelegate, mClassName, name, desc, isStatic); + mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod, + mIsStaticInnerClass); // A native has no code to visit, so we need to generate it directly. a.generateDelegateCode(); @@ -120,6 +137,7 @@ public class DelegateClassAdapter extends ClassVisitor { desc, signature, exceptions); return new DelegateMethodAdapter( - mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic); + mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod, + mIsStaticInnerClass); } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java index 12690db547a9..cca9e574b7ea 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -85,6 +85,8 @@ class DelegateMethodAdapter extends MethodVisitor { private String mDesc; /** True if the original method is static. */ private final boolean mIsStatic; + /** True if the method is contained in a static inner class */ + private final boolean mIsStaticInnerClass; /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */ private final String mClassName; /** The method name. */ @@ -120,7 +122,8 @@ class DelegateMethodAdapter extends MethodVisitor { String className, String methodName, String desc, - boolean isStatic) { + boolean isStatic, + boolean isStaticClass) { super(Opcodes.ASM4); mLog = log; mOrgWriter = mvOriginal; @@ -129,6 +132,7 @@ class DelegateMethodAdapter extends MethodVisitor { mMethodName = methodName; mDesc = desc; mIsStatic = isStatic; + mIsStaticInnerClass = isStaticClass; } /** @@ -206,7 +210,7 @@ class DelegateMethodAdapter extends MethodVisitor { // by the 'this' of any outer class, if any. if (!mIsStatic) { - if (outerType != null) { + if (outerType != null && !mIsStaticInnerClass) { // The first-level inner class has a package-protected member called 'this$0' // that points to the outer class. diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index 648cea430de2..e37a09b348b8 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.fail; import com.android.tools.layoutlib.create.dataclass.ClassWithNative; import com.android.tools.layoutlib.create.dataclass.OuterClass; import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; +import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; import org.junit.Before; import org.junit.Test; @@ -56,6 +57,8 @@ public class DelegateClassAdapterTest { private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName(); private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" + InnerClass.class.getSimpleName(); + private static final String STATIC_INNER_CLASS_NAME = + OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName(); @Before public void setUp() throws Exception { @@ -294,6 +297,61 @@ public class DelegateClassAdapterTest { } } + @Test + public void testDelegateStaticInner() throws Throwable { + // We'll delegate the "get" method of both the inner and outer class. + HashSet<String> delegateMethods = new HashSet<String>(); + delegateMethods.add("get"); + + // Generate the delegate for the outer class. + ClassWriter cwOuter = new ClassWriter(0 /*flags*/); + String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvOuter = new DelegateClassAdapter( + mLog, cwOuter, outerClassName, delegateMethods); + ClassReader cr = new ClassReader(OUTER_CLASS_NAME); + cr.accept(cvOuter, 0 /* flags */); + + // Generate the delegate for the static inner class. + ClassWriter cwInner = new ClassWriter(0 /*flags*/); + String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvInner = new DelegateClassAdapter( + mLog, cwInner, innerClassName, delegateMethods); + cr = new ClassReader(STATIC_INNER_CLASS_NAME); + cr.accept(cvInner, 0 /* flags */); + + // Load the generated classes in a different class loader and try them + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + + // Check the outer class + Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); + Object o2 = outerClazz2.newInstance(); + assertNotNull(o2); + + // Check the inner class. Since it's not a static inner class, we need + // to use the hidden constructor that takes the outer class as first parameter. + Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME); + Constructor<?> innerCons = innerClazz2.getConstructor(); + Object i2 = innerCons.newInstance(); + assertNotNull(i2); + + // The original StaticInner.get returns 100+10+20, + // but the delegate makes it return 6+10+20 + assertEquals(6+10+20, callGet(i2, 10, 20)); + assertEquals(100+10+20, callGet_Original(i2, 10, 20)); + } + }; + cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); + cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray()); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + //------- /** diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java index f083e76d995c..6dfb81662e40 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -45,6 +45,16 @@ public class OuterClass { } } + public static class StaticInnerClass { + public StaticInnerClass() { + } + + // StaticInnerClass.get returns 100 + a + b + public int get(int a, long b) { + return 100 + a + (int) b; + } + } + @SuppressWarnings("unused") private String privateMethod() { return "outerPrivateMethod"; diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java new file mode 100644 index 000000000000..a29439ee3fee --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * 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.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; +import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_StaticInnerClass_Delegate { + // The delegate override of Inner.get return 6 + a + b + public static int get(StaticInnerClass inner, int a, long b) { + return 6 + a + (int) b; + } +} |