Merge "Fixed typos."
diff --git a/api/current.txt b/api/current.txt
index 1a7139c..e17bf28 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2732,6 +2732,7 @@
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";
}
@@ -2796,6 +2797,7 @@
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";
@@ -2812,6 +2814,8 @@
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";
@@ -3773,6 +3777,8 @@
}
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);
@@ -3782,6 +3788,7 @@
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";
diff --git a/api/system-current.txt b/api/system-current.txt
index f2784410..d8b9b6f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2831,6 +2831,7 @@
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";
}
@@ -2895,6 +2896,7 @@
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";
@@ -2911,6 +2913,8 @@
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";
@@ -3882,6 +3886,8 @@
}
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);
@@ -3891,6 +3897,7 @@
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";
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 62e0919a..daf01ec 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.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 @@
" 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 @@
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 @@
}
}
+ 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 1e15204..6801ce9 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 407e551..25d2031 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 c9b9e58..0000000
--- 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/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 9c401c7f..185ceb4 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -116,6 +116,25 @@
*/
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 @@
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 @@
}).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 0a7568a..42e5e2a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -240,6 +240,20 @@
*/
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 @@
}
}
}
+
+ /**
+ * 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 58612da..b326070 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -83,4 +83,11 @@
*/
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 0d95db1..5de311e 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -83,4 +83,9 @@
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 d8d2737..20d71a6 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -250,50 +250,19 @@
/**
* 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 @@
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 @@
(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 @@
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);
+ PathParser.PathData pathDataFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData pathDataTo = toString == null
+ ? null : new PathParser.PathData(toString);
- if (nodesFrom != null) {
- if (nodesTo != null) {
- anim.setObjectValues(nodesFrom, nodesTo);
- if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ 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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 00bba2d..f7aee75 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -528,6 +528,15 @@
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 @@
if (mIcon != null) {
return mIcon;
}
- return loadTaskDescriptionIcon(mIconFilename);
+ return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
}
/** @hide */
@@ -898,11 +907,11 @@
}
/** @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 e246e62..0b7b6fc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1962,6 +1962,16 @@
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 @@
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 @@
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 @@
}
@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 57900aa..cee1aa5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -64,11 +64,13 @@
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 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 @@
} 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 @@
}
}
- /** @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 @@
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 @@
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/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c0cc566..20f3495 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -575,14 +575,20 @@
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 4b670cd..e93b40e 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -470,6 +470,11 @@
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 @@
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 3d0fc92..db4f5c1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -390,6 +390,7 @@
// 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 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 @@
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 30232da..84ddd9f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -50,9 +50,6 @@
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 390c280..4e6548b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -838,13 +838,6 @@
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 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/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index eda4136..1996e0f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -982,7 +982,7 @@
.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 566de4e..42fef3b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -240,16 +240,15 @@
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 2e43ffc..c6510f0 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1301,23 +1301,6 @@
}
@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 @@
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 @@
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 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 2d9090b..db12564 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -33,6 +33,7 @@
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 @@
/** {@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 @@
}
/** {@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/util/PathParser.java b/core/java/android/util/PathParser.java
index f099479..f17a16c 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 @@
}
/**
- * @param pathData The string representing a path, the same as "d" string in svg file.
- * @return an array of the PathDataNode.
+ * Interpret PathData as path commands and insert the commands to the given path.
+ *
+ * @param data The source PathData to be converted.
+ * @param outPath The Path object where path commands will be inserted.
*/
- 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()]);
+ public static void createPathFromPathData(Path outPath, PathData data) {
+ nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData);
}
/**
- * @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
+ * @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>
*/
- 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;
+ public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) {
+ return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData);
}
/**
- * Update the target's data to match the source.
- * Before calling this, make sure canMorph(target, source) is true.
+ * 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 PathData {
+ long mNativePathData = 0;
+ public PathData() {
+ mNativePathData = nCreateEmptyPathData();
+ }
+
+ public PathData(PathData data) {
+ mNativePathData = nCreatePathData(data.mNativePathData);
+ }
+
+ public PathData(String pathString) {
+ mNativePathData = nCreatePathDataFromString(pathString, pathString.length());
+ if (mNativePathData == 0) {
+ throw new IllegalArgumentException("Invalid pathData: " + pathString);
+ }
+ }
+
+ /**
+ * Update the path data to match the source.
+ * Before calling this, make sure canMorph(target, source) is true.
+ *
+ * @param source The source path represented in PathData
+ */
+ public void setPathData(PathData source) {
+ nSetPathData(mNativePathData, source.mNativePathData);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mNativePathData != 0) {
+ nFinalize(mNativePathData);
+ mNativePathData = 0;
+ }
+ super.finalize();
+ }
+ }
+
+ /**
+ * 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 target The target path represented in an array of PathDataNode
- * @param source The source path represented in an array of PathDataNode
+ * @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 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];
- }
- }
+ public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData,
+ float fraction) {
+ return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData,
+ toData.mNativePathData, fraction);
}
- 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;
- }
-
- /**
- * 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 '-'.
- */
- 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;
- }
-
- /**
- * Each PathDataNode represents one command in the "d" attribute of the svg
- * file.
- * An array of PathDataNode can represent the whole "d" attribute.
- */
- public static class PathDataNode {
- private char mType;
- private float[] mParams;
-
- private PathDataNode(char type, float[] params) {
- mType = type;
- mParams = params;
- }
-
- private PathDataNode(PathDataNode n) {
- mType = n.mType;
- mParams = Arrays.copyOf(n.mParams, n.mParams.length);
- }
-
- /**
- * 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;
- }
- }
-
- /**
- * The current PathDataNode 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 PathDataNode.
- * @param nodeTo The end value as a PathDataNode
- * @param fraction The fraction to interpolate.
- */
- 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;
- }
- }
-
- 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;
- }
- current[0] = currentX;
- current[1] = currentY;
- current[2] = ctrlPointX;
- current[3] = ctrlPointY;
- current[4] = currentSegmentStartX;
- current[5] = currentSegmentStartY;
- }
-
- 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;
- }
- }
- }
-
+ // 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/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9428f44..19a98f3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -540,6 +540,13 @@
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/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 077cebc..4c221fc5 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.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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 void startChanging() {
+ void startChanging() {
mChanging = true;
}
- public void finishChanging() {
+ void finishChanging() {
mChanging = false;
drawableChanged();
}
@@ -1138,7 +1154,7 @@
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 @@
if (mFloatingActionMode != null) {
mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
}
+
+ updateElevation();
}
@Override
@@ -1495,38 +1513,19 @@
}
/**
- * 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 @@
}
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,
+ mDecorCaptionView.addView(root,
new ViewGroup.LayoutParams(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 @@
}
/**
- * 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,12 @@
}
void clearContentView() {
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getChildCount() > 1) {
- mNonClientDecorView.removeViewAt(1);
+ if (mDecorCaptionView != null) {
+ if (mDecorCaptionView.getChildCount() > 1) {
+ mDecorCaptionView.removeViewAt(1);
}
} 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 +1746,60 @@
}
}
+ /**
+ * 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/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
new file mode 100644
index 0000000..e22bd10
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -0,0 +1,205 @@
+/*
+ * 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.widget;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
+import android.content.Context;
+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 com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * 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>
+ * 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>setOutlineProvider</li>
+ * <li>setSurfaceFormat</li>
+ * <li>..</li>
+ * </ul>
+ */
+public class DecorCaptionView extends LinearLayout
+ implements View.OnClickListener, View.OnTouchListener {
+ private final static String TAG = "DecorCaptionView";
+ private PhoneWindow mOwner = null;
+ private boolean mShow = false;
+
+ // True if the window is being dragged.
+ private boolean mDragging = false;
+
+ // True when the left mouse button got released while dragging.
+ private boolean mLeftMouseButtonReleased;
+
+ public DecorCaptionView(Context context) {
+ super(context);
+ }
+
+ public DecorCaptionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setPhoneWindow(PhoneWindow owner, boolean show) {
+ mOwner = owner;
+ mShow = show;
+ updateCaptionVisibility();
+ // By changing the outline provider to BOUNDS, the window can remove its
+ // background without removing the shadow.
+ mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+
+ findViewById(R.id.maximize_window).setOnClickListener(this);
+ findViewById(R.id.close_window).setOnClickListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
+ // the old input device events get cancelled first. So no need to remember the kind of
+ // input device we are listening to.
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ 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
+ // either a left mouse button or any other input device.
+ if (!mDragging &&
+ (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
+ mDragging = true;
+ mLeftMouseButtonReleased = false;
+ startMovingTask(e.getRawX(), e.getRawY());
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mDragging && !mLeftMouseButtonReleased) {
+ if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
+ // There is no separate mouse button up call and if the user mixes mouse
+ // button drag actions, we stop dragging once he releases the button.
+ mLeftMouseButtonReleased = true;
+ break;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (!mDragging) {
+ break;
+ }
+ // Abort the ongoing dragging.
+ mDragging = false;
+ return true;
+ }
+ return mDragging;
+ }
+
+ /**
+ * 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();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.maximize_window) {
+ maximizeWindow();
+ } else if (view.getId() == R.id.close_window) {
+ mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+ }
+ }
+
+ @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("DecorCaptionView can only handle 1 client view");
+ }
+ super.addView(child, index, params);
+ }
+
+ /**
+ * Determine if the workspace is entirely covered by the window.
+ * @return Returns true when the window is filling the entire screen/workspace.
+ **/
+ private boolean isFillingScreen() {
+ return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
+ (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
+ }
+
+ /**
+ * Updates the visibility of the caption.
+ **/
+ private void updateCaptionVisibility() {
+ // Don't show the caption if the window has e.g. entered full screen.
+ boolean invisible = isFillingScreen() || !mShow;
+ View caption = getChildAt(0);
+ caption.setVisibility(invisible ? GONE : VISIBLE);
+ caption.setOnTouchListener(this);
+ }
+
+ /**
+ * Maximize the window by moving it to the maximized workspace stack.
+ **/
+ private void maximizeWindow() {
+ Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+ if (callback != null) {
+ try {
+ callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Cannot change task workspace.");
+ }
+ }
+ }
+
+ public boolean isCaptionShowing() {
+ return mShow;
+ }
+
+ public int getCaptionHeight() {
+ final View caption = getChildAt(0);
+ return (caption != null) ? caption.getHeight() : 0;
+ }
+}
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
deleted file mode 100644
index 33b8e05..0000000
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ /dev/null
@@ -1,310 +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.
- */
-
-package com.android.internal.widget;
-
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
-import android.content.Context;
-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.
- * 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
- 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 PhoneWindow mOwner = null;
- private boolean mWindowHasShadow = false;
- private boolean mShowDecor = false;
-
- // True if the window is being dragged.
- private boolean mDragging = false;
-
- // 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;
-
- // 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;
-
- public NonClientDecorView(Context context) {
- super(context);
- }
-
- public NonClientDecorView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
- mOwner = owner;
- mWindowHasShadow = windowHasShadow;
- mShowDecor = showDecor;
- 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);
-
- findViewById(R.id.maximize_window).setOnClickListener(this);
- findViewById(R.id.close_window).setOnClickListener(this);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent e) {
- // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
- // the old input device events get cancelled first. So no need to remember the kind of
- // 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.
- return false;
- }
- // A drag action is started if we aren't dragging already and the starting event is
- // either a left mouse button or any other input device.
- if (!mDragging &&
- (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
- mDragging = true;
- mLeftMouseButtonReleased = false;
- startMovingTask(e.getRawX(), e.getRawY());
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mDragging && !mLeftMouseButtonReleased) {
- if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
- // There is no separate mouse button up call and if the user mixes mouse
- // button drag actions, we stop dragging once he releases the button.
- mLeftMouseButtonReleased = true;
- break;
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (!mDragging) {
- break;
- }
- // Abort the ongoing dragging.
- mDragging = false;
- return true;
- }
- return mDragging;
- }
-
- /**
- * 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;
- updateCaptionVisibility();
- if (windowHasShadow != mWindowHasShadow) {
- mWindowHasShadow = windowHasShadow;
- initializeElevation();
- }
- }
-
- @Override
- public void onClick(View view) {
- if (view.getId() == R.id.maximize_window) {
- maximizeWindow();
- } else if (view.getId() == R.id.close_window) {
- mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- mWindowHasFocus = hasWindowFocus;
- updateElevation();
- super.onWindowFocusChanged(hasWindowFocus);
- }
-
- @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;
-
- super.onLayout(changed, left, top, right, bottom);
- }
-
- @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");
- }
- super.addView(child, index, params);
- }
-
- /**
- * Determine if the workspace is entirely covered by the window.
- * @return Returns true when the window is filling the entire screen/workspace.
- **/
- private boolean isFillingScreen() {
- return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
- (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
- }
-
- /**
- * 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());
- }
-
- /**
- * Maximize the window by moving it to the maximized workspace stack.
- **/
- private void maximizeWindow() {
- Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
- if (callback != null) {
- try {
- callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
- } catch (RemoteException ex) {
- Log.e(TAG, "Cannot change task workspace.");
- }
- }
- }
-
- public boolean isShowingDecor() {
- return mShowDecor;
- }
-
- public int getDecorCaptionHeight() {
- final View caption = getChildAt(0);
- return (caption != null) ? caption.getHeight() : 0;
- }
-}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 65e8058..bd41c5d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -580,7 +580,8 @@
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 @@
* 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 245aa0f..0927120 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 @@
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 3f1be456..73c7487 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
if (capset(&capheader, &capdata[0]) == -1) {
ALOGE("capset(%" PRId64 ", %" PRId64 ") failed", permitted, effective);
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "capset failed");
}
}
@@ -259,7 +258,7 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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
similarity index 84%
rename from core/res/res/drawable/non_client_decor_title.xml
rename to core/res/res/drawable/decor_caption_title.xml
index e50daea..591605d3 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
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_focused.xml
rename to 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
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_unfocused.xml
rename to 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
similarity index 90%
rename from core/res/res/layout/non_client_decor_dark.xml
rename to core/res/res/layout/decor_caption_dark.xml
index 40b8960..86d68af 100644
--- a/core/res/res/layout/non_client_decor_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -17,7 +17,7 @@
*/
-->
-<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"
@@ -26,7 +26,7 @@
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 +53,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
similarity index 90%
rename from core/res/res/layout/non_client_decor_light.xml
rename to core/res/res/layout/decor_caption_light.xml
index c75d526..ee03545 100644
--- a/core/res/res/layout/non_client_decor_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -17,7 +17,7 @@
*/
-->
-<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"
@@ -26,7 +26,7 @@
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 +53,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/symbols.xml b/core/res/res/values/symbols.xml
index 556467a..7ce9d8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1957,9 +1957,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/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index a50c945..0ee877e 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1424,7 +1424,7 @@
}
}
- 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 eee9b24..f630055e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -231,6 +231,9 @@
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 @@
* 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 @@
@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 @@
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 @@
mDpiScaledHeight = (int) pathRenderer.mBaseHeight;
mDpiScaledInsets = opticalInsets;
}
+
+ mDpiScaledDirty = false;
}
@Override
@@ -481,6 +499,11 @@
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 @@
} finally {
a.recycle();
}
+
+ // May have changed size.
+ mDpiScaledDirty = true;
}
// Apply theme to contained color state list.
@@ -499,7 +525,6 @@
state.mTint = state.mTint.obtainForTheme(t);
}
- final VPathRenderer path = state.mVPathRenderer;
if (path != null && path.canApplyTheme()) {
path.applyTheme(t);
}
@@ -565,21 +590,24 @@
}
@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 @@
// 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 @@
}
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 @@
}
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 @@
}
}
- 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 @@
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 @@
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 @@
* 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 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 @@
/* 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 @@
* @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 @@
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 @@
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 8565372..92226f5 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -29,6 +29,7 @@
utils/NinePatchImpl.cpp \
utils/StringUtils.cpp \
utils/TestWindowContext.cpp \
+ utils/VectorDrawableUtils.cpp \
AmbientShadow.cpp \
AnimationContext.cpp \
Animator.cpp \
@@ -218,7 +219,7 @@
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
diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp
index 35230ff..4e9ac9c 100644
--- a/libs/hwui/PathParser.cpp
+++ b/libs/hwui/PathParser.cpp
@@ -221,7 +221,7 @@
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 a9c1e60..4c87b18 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 @@
*/
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 716d536..3f24f44 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -288,6 +288,9 @@
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 05ea2da..c9a54ca 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 40ce986..2e56349 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 @@
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/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp
index 171078d..3d9fafa 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, pathString, length);
+ 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::getPathDataFromString(&outData, &result, sPathString, length);
}
StopBenchmarkTiming();
}
diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/VectorDrawableTests.cpp
similarity index 79%
rename from libs/hwui/unit_tests/PathParserTests.cpp
rename to libs/hwui/unit_tests/VectorDrawableTests.cpp
index c99d7b0..77dd73a 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 @@
{"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, 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(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/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp
new file mode 100644
index 0000000..ca75c59
--- /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 0000000..21c1cdc
--- /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/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index f0f8161..7c0676f 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.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 @@
private MtpManager mMtpManager;
private ContentResolver mResolver;
+ @GuardedBy("mDeviceToolkits")
private Map<Integer, DeviceToolkit> mDeviceToolkits;
private RootScanner mRootScanner;
private Resources mResources;
@@ -222,9 +224,11 @@
@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 @@
}
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 @@
}
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 @@
}
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 2d1af26..723dc14 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -67,10 +67,10 @@
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 @@
unregisterReceiver(mReceiver);
mReceiver = null;
try {
- provider.close();
+ provider.closeAllDevices();
} catch (InterruptedException e) {
Log.e(MtpDocumentsProvider.TAG, e.getMessage());
}
@@ -105,8 +105,8 @@
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 d9ed4ab..b0962dd 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.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 @@
*/
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 @@
mDatabase = database;
}
+ /**
+ * Notifies a change of the roots list via ContentResolver.
+ */
void notifyChange() {
final Uri uri =
DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
@@ -56,74 +68,81 @@
* Starts to check new changes right away.
* If the background thread has already gone, it restarts another background thread.
*/
- synchronized void scanNow() {
- if (mClosed) {
+ synchronized void resume() {
+ if (mExecutor == null) {
+ // Only single thread updates the database.
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+ if (mCurrentTask != null) {
+ // Cancel previous task.
+ mCurrentTask.cancel(true);
+ }
+ mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
+ mExecutor.submit(mCurrentTask);
+ }
+
+ /**
+ * Stops background thread and wait for its termination.
+ * @throws InterruptedException
+ */
+ synchronized void pause() throws InterruptedException {
+ if (mExecutor == null) {
return;
}
- mPollingCount = 0;
- if (mBackgroundThread == null) {
- mBackgroundThread = new BackgroundLoaderThread();
- mBackgroundThread.start();
- } else {
- notify();
+ mExecutor.shutdownNow();
+ if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread.");
}
+ mExecutor = null;
}
- void close() throws InterruptedException {
- Thread thread;
- synchronized (this) {
- mClosed = true;
- thread = mBackgroundThread;
- if (mBackgroundThread == null) {
- return;
- }
- notify();
- }
- thread.join();
- }
-
- 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 82e08cd..cabb08d 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 @@
@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 c7cf61a..5a6f1d1 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/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index fe67fd9..965e7a67 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 @@
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 4ecb80a..f3c66a5 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.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 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 @@
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 @@
// 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 d3d9bef..f8ab35f 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 @@
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/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index c131628..a5cef1a 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.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 @@
}
}
- 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 @@
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 485e26b..f5ed83e 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -107,6 +107,8 @@
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 @@
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 @@
*/
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 @@
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 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 @@
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 @@
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 @@
mState = STATE_ACTIVE;
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+ mCurIdleBudget = 0;
+ mMaintenanceStartTime = 0;
resetIdleManagementLocked();
resetLightIdleManagementLocked();
+ addEvent(EVENT_NORMAL);
}
}
@@ -1496,21 +1545,43 @@
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 @@
cancelLightAlarmLocked();
}
EventLogTags.writeDeviceIdle(mState, reason);
+ addEvent(EVENT_FULL_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE:
@@ -1612,6 +1684,7 @@
(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 @@
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 @@
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 @@
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 @@
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 ae9ddc0..40f7c5c 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1196,6 +1196,9 @@
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 6acec6b..eb49a78 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -389,7 +389,7 @@
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 f89155d..a32bb2f 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -1904,16 +1904,18 @@
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- synchronized (mLock) {
- if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
- mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
- }
- if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
- // TODO: persist through vold and reboot
- }
+ 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));
+ }
- writeSettingsLocked();
- mHandler.obtainMessage(H_RESET).sendToTarget();
+ if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
+ synchronized (mLock) {
+ mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
+
+ writeSettingsLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ }
}
}
@@ -2738,7 +2740,7 @@
@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 @@
}
@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 9ac4ba3..37d47c3 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 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 @@
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 @@
}
}
+ @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 @@
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 @@
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 0000000..2b59b74
--- /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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e6bfb7..3a0d80b 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.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.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.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.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.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.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 @@
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 @@
}
@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 @@
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;
- // Transfer any global setting for forcing RTL layout, into a System Property
- SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+ final boolean freeformWindowManagement =
+ mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
- Configuration configuration = new Configuration();
+ 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(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+
+ 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 @@
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 @@
}
@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 aa04bd7..ea8a12f 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.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 @@
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 0ec4b18..124d2ef 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.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 @@
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 @@
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 @@
}
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 @@
"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 150baf0..9a00075 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.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.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
import libcore.io.IoUtils;
@@ -54,8 +55,10 @@
/** 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
}
}
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 @@
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 @@
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 @@
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 @@
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 @@
}
if (!DEBUG) {
- removeObsoleteFiles(recoveredTaskIds);
+ removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
+ }
+ return tasks;
+ }
+
+ ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) {
+ final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+
+ for (int userId : validUserIds) {
+ tasks.addAll(restoreTasksForUserLocked(userId));
}
- // Fixup task affiliation from taskIds
+ // 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 @@
}
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 @@
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 @@
}
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 @@
INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
}
-
while (mWriteQueue.isEmpty()) {
if (mNextWriteTime != 0) {
mNextWriteTime = 0; // idle.
@@ -542,15 +580,15 @@
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 @@
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 b214080..3fc6846 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_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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
mLastThumbnailFile.delete();
}
} else {
- mService.mTaskPersister.saveImage(thumbnail, mFilename);
+ mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
}
return true;
}
@@ -549,7 +560,8 @@
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 @@
// 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 d6fced6..e04f138 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.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 @@
/**
* 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 @@
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 @@
}
} 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 @@
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 fd1e9dd..4424838b 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 @@
}
@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 @@
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 803db10..aea137b 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 @@
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 66381f5..f8b661f 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 0420269..c9e1315 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.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 @@
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 @@
// 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 @@
@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 @@
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 8fac9da..073b4f03 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 @@
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 @@
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 @@
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 @@
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 @@
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 992919e..e41a976 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3021,18 +3021,19 @@
* 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();
@@ -6302,22 +6303,25 @@
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 +6891,6 @@
+ 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 +6923,10 @@
}
}
+ // 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 +6937,8 @@
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
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1d299d7..99aa30b 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 @@
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 53ce9e2..baeccb4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -392,7 +392,7 @@
@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 121ef21..ae6874f 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 @@
}
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 943c9ed..8b1a830 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 @@
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 3ad2610..253fdad 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 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 @@
}
}
- 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 31c3670..844cca5 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 @@
}
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 e0f95cf..c734fab 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 @@
}
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/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index ae4a57d..7ef7566 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 @@
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 @@
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 @@
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 @@
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 12690db..cca9e57 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 @@
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 @@
String className,
String methodName,
String desc,
- boolean isStatic) {
+ boolean isStatic,
+ boolean isStaticClass) {
super(Opcodes.ASM4);
mLog = log;
mOrgWriter = mvOriginal;
@@ -129,6 +132,7 @@
mMethodName = methodName;
mDesc = desc;
mIsStatic = isStatic;
+ mIsStaticInnerClass = isStaticClass;
}
/**
@@ -206,7 +210,7 @@
// 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 648cea43..e37a09b 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 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 @@
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 @@
}
}
+ @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 f083e76..6dfb816 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 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 0000000..a29439e
--- /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;
+ }
+}