Merge "Apply VectorDrawable density scaling before applying theme"
diff --git a/api/current.txt b/api/current.txt
index 6208640..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";
@@ -36238,6 +36245,7 @@
method public boolean canResolveTextDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
+ method public final void cancelDrag();
method public void cancelLongPress();
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
diff --git a/api/system-current.txt b/api/system-current.txt
index fc11f34..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";
@@ -38559,6 +38566,7 @@
method public boolean canResolveTextDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
+ method public final void cancelDrag();
method public void cancelLongPress();
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
index a675769..726167e 100644
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
@@ -291,7 +291,7 @@
System.err.println(" settings [--user NUM] delete namespace key");
System.err.println(" settings [--user NUM] list namespace");
System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
- System.err.println("If '--user NUM' is not given, the operations are performed on the"
+ System.err.println("If '--user NUM' is not given, the operations are performed on the "
+ "system user.");
}
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 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..084319a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -884,7 +884,7 @@
if (mIcon != null) {
return mIcon;
}
- return loadTaskDescriptionIcon(mIconFilename);
+ return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
}
/** @hide */
@@ -898,11 +898,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..16cf254 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2490,7 +2490,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);
@@ -5998,11 +5999,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..88543e5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -497,7 +497,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;
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 b5b77e6..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;
@@ -3271,12 +3238,16 @@
* Construct a RemoteViews for the final big notification layout.
*/
public RemoteViews makeBigContentView() {
- if (mStyle != null) {
+ if (mN.bigContentView != null) {
+ return mN.bigContentView;
+ } else if (mStyle != null) {
final RemoteViews styleView = mStyle.makeBigContentView();
if (styleView != null) {
return styleView;
}
- } else if (mActions.size() == 0) return null;
+ } else if (mActions.size() == 0) {
+ return null;
+ }
return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
@@ -3285,12 +3256,17 @@
* Construct a RemoteViews for the final heads-up notification layout.
*/
public RemoteViews makeHeadsUpContentView() {
- if (mStyle != null) {
- final RemoteViews styleView = mStyle.makeHeadsUpContentView();
- if (styleView != null) {
- return styleView;
- }
- } else if (mActions.size() == 0) return null;
+ if (mN.headsUpContentView != null) {
+ return mN.headsUpContentView;
+ } else if (mStyle != null) {
+ final RemoteViews styleView = mStyle.makeHeadsUpContentView();
+ if (styleView != null) {
+ return styleView;
+ }
+ } else if (mActions.size() == 0) {
+ return null;
+ }
+
return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index c368e5a..5997515 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -435,7 +435,7 @@
return null;
}
- final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
+ final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(uri);
intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 159ca01..af7f472 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -125,8 +125,7 @@
public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
/** {@hide} */
- public static final String
- ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT";
+ public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
/** {@hide} */
public static final String
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index 463eb5b..ebe3f47 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -52,9 +52,21 @@
}
public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp, int userHandle)
- throws PackageManager.NameNotFoundException, RemoteException {
- this(pm, AppGlobals.getPackageManager().getServiceInfo(comp,
- PackageManager.GET_META_DATA, userHandle));
+ throws PackageManager.NameNotFoundException {
+ this(pm, getServiceInfoOrThrow(comp, userHandle));
+ }
+
+ static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
+ throws PackageManager.NameNotFoundException {
+ try {
+ ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(comp,
+ PackageManager.GET_META_DATA, userHandle);
+ if (si != null) {
+ return si;
+ }
+ } catch (RemoteException e) {
+ }
+ throw new PackageManager.NameNotFoundException(comp.toString());
}
public VoiceInteractionServiceInfo(PackageManager pm, ServiceInfo si) {
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/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3fc70cc..f81b5d0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,6 +186,11 @@
void reportDropResult(IWindow window, boolean consumed);
/**
+ * Cancel a drag operation.
+ */
+ void cancelDrag(IBinder dragToken);
+
+ /**
* Tell the OS that we've just dragged into a View that is willing to accept the drop
*/
void dragRecipientEntered(IWindow window);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index de4d439..30408c6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19905,11 +19905,11 @@
}
Surface surface = new Surface();
try {
- IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+ mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
flags, shadowSize.x, shadowSize.y, surface);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
- + " surface=" + surface);
- if (token != null) {
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
+ + mAttachInfo.mDragToken + " surface=" + surface);
+ if (mAttachInfo.mDragToken != null) {
Canvas canvas = surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -19926,7 +19926,7 @@
// repurpose 'shadowSize' for the last touch point
root.getLastTouchPoint(shadowSize);
- okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+ okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
shadowSize.x, shadowSize.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
@@ -19943,6 +19943,22 @@
return okay;
}
+ public final void cancelDrag() {
+ if (ViewDebug.DEBUG_DRAG) {
+ Log.d(VIEW_LOG_TAG, "cancelDrag");
+ }
+ if (mAttachInfo.mDragToken != null) {
+ try {
+ mAttachInfo.mSession.cancelDrag(mAttachInfo.mDragToken);
+ } catch (Exception e) {
+ Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
+ }
+ mAttachInfo.mDragToken = null;
+ } else {
+ Log.e(VIEW_LOG_TAG, "No active drag to cancel");
+ }
+ }
+
/**
* Starts a move from {startX, startY}, the amount of the movement will be the offset
* between {startX, startY} and the new cursor positon.
@@ -22221,6 +22237,11 @@
View mViewRequestingLayout;
/**
+ * Used to track the identity of the current drag operation.
+ */
+ IBinder mDragToken;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cd9dd97..f1d9f1ab 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5307,10 +5307,10 @@
}
}
- // When the drag operation ends, release any local state object
- // that may have been in use
+ // When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) {
setLocalDragState(null);
+ mAttachInfo.mDragToken = null;
}
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 122b83a..19a98f3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -23,6 +23,7 @@
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodClient;
import android.annotation.RequiresPermission;
import android.content.Context;
@@ -440,8 +441,11 @@
}
case MSG_UNBIND: {
final int sequence = msg.arg1;
+ @InputMethodClient.UnbindReason
+ final int reason = msg.arg2;
if (DEBUG) {
- Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence);
+ Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence +
+ " reason=" + InputMethodClient.getUnbindReason(reason));
}
final boolean startInput;
synchronized (mH) {
@@ -536,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() {
@@ -569,8 +580,8 @@
}
@Override
- public void onUnbindMethod(int sequence) {
- mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, 0));
+ public void onUnbindMethod(int sequence, @InputMethodClient.UnbindReason int unbindReason) {
+ mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, unbindReason));
}
@Override
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 5eea252..47df4e8 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -177,8 +177,7 @@
public void onConfigurationChanged(Configuration newConfig) {
if (!mMaxItemsSet) {
- mMaxItems = mContext.getResources().getInteger(
- com.android.internal.R.integer.max_action_buttons);
+ mMaxItems = ActionBarPolicy.get(mContext).getMaxActionButtons();
}
if (mMenu != null) {
mMenu.onItemsChanged(true);
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index a018d26..9f94005 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -28,6 +28,8 @@
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.RemotableViewMethod;
@@ -447,6 +449,68 @@
return CheckedTextView.class.getName();
}
+ static class SavedState extends BaseSavedState {
+ boolean checked;
+
+ /**
+ * Constructor called from {@link CheckedTextView#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ checked = (Boolean)in.readValue(null);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeValue(checked);
+ }
+
+ @Override
+ public String toString() {
+ return "CheckedTextView.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " checked=" + checked + "}";
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+
+ ss.checked = isChecked();
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+ setChecked(ss.checked);
+ requestLayout();
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b5d994d..3e6d121 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3366,10 +3366,17 @@
}
/**
- * Sets whether the movement method will automatically be set to
- * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
- * set to nonzero and links are detected in {@link #setText}.
- * The default is true.
+ * Sets whether the movement method will automatically be set to {@link LinkMovementMethod}
+ * after {@link #setText} or {@link #append} is called. The movement method is set if one of the
+ * following is true:
+ * <ul>
+ * <li>{@link #setAutoLinkMask} has been set to nonzero and links are detected in
+ * {@link #setText} or {@link #append}.
+ * <li>The input for {@link #setText} or {@link #append} contains a {@link ClickableSpan}.
+ * </ul>
+ *
+ * <p>This function does not have an immediate effect, movement method will be set only after a
+ * call to {@link #setText} or {@link #append}. The default is true.</p>
*
* @attr ref android.R.styleable#TextView_linksClickable
*/
@@ -3379,10 +3386,14 @@
}
/**
- * Returns whether the movement method will automatically be set to
- * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
- * set to nonzero and links are detected in {@link #setText}.
- * The default is true.
+ * Returns whether the movement method will automatically be set to {@link LinkMovementMethod}
+ * after {@link #setText} or {@link #append} is called.
+ *
+ * See {@link #setLinksClickable} for details.
+ *
+ * <p>The default is true.</p>
+ *
+ * @see #setLinksClickable
*
* @attr ref android.R.styleable#TextView_linksClickable
*/
@@ -3976,13 +3987,19 @@
((Editable) mText).append(text, start, end);
+ boolean hasClickableSpans = false;
if (mAutoLinkMask != 0) {
- boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
- // Do not change the movement method for text that support text selection as it
- // would prevent an arbitrary cursor displacement.
- if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
- setMovementMethod(LinkMovementMethod.getInstance());
- }
+ hasClickableSpans = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+ } else if (mLinksClickable && text instanceof Spanned) {
+ ClickableSpan[] clickableSpans =
+ ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+ hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
+ }
+
+ // Do not change the movement method for text that supports text selection as it
+ // would prevent an arbitrary cursor displacement.
+ if (hasClickableSpans && mLinksClickable && !textCanBeSelected()) {
+ setMovementMethod(LinkMovementMethod.getInstance());
}
}
@@ -4327,6 +4344,7 @@
text = TextUtils.stringOrSpannedString(text);
}
+ boolean hasClickableSpans = false;
if (mAutoLinkMask != 0) {
Spannable s2;
@@ -4336,22 +4354,32 @@
s2 = mSpannableFactory.newSpannable(text);
}
- if (Linkify.addLinks(s2, mAutoLinkMask)) {
+ hasClickableSpans = Linkify.addLinks(s2, mAutoLinkMask);
+ if (hasClickableSpans) {
text = s2;
- type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
+ }
+ } else if (mLinksClickable && text instanceof Spanned) {
+ ClickableSpan[] clickableSpans =
+ ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
+ hasClickableSpans = clickableSpans != null && clickableSpans.length > 0;
+ if (hasClickableSpans && !(text instanceof Spannable)) {
+ text = mSpannableFactory.newSpannable(text);
+ }
+ }
- /*
- * We must go ahead and set the text before changing the
- * movement method, because setMovementMethod() may call
- * setText() again to try to upgrade the buffer type.
- */
- mText = text;
+ if (hasClickableSpans) {
+ type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
+ /*
+ * We must go ahead and set the text before changing the
+ * movement method, because setMovementMethod() may call
+ * setText() again to try to upgrade the buffer type.
+ */
+ mText = text;
- // Do not change the movement method for text that support text selection as it
- // would prevent an arbitrary cursor displacement.
- if (mLinksClickable && !textCanBeSelected()) {
- setMovementMethod(LinkMovementMethod.getInstance());
- }
+ // Do not change the movement method for text that supports text selection as it
+ // would prevent an arbitrary cursor displacement.
+ if (mLinksClickable && !textCanBeSelected()) {
+ setMovementMethod(LinkMovementMethod.getInstance());
}
}
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/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index bee59dc..007ab29 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -19,6 +19,7 @@
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
@@ -38,10 +39,29 @@
mContext = context;
}
+ /**
+ * Returns the maximum number of action buttons that should be permitted within an action
+ * bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit.
+ * "always" items can override this.
+ */
public int getMaxActionButtons() {
- return mContext.getResources().getInteger(R.integer.max_action_buttons);
+ final Configuration config = mContext.getResources().getConfiguration();
+ final int width = config.screenWidthDp;
+ final int height = config.screenHeightDp;
+ final int smallest = config.smallestScreenWidthDp;
+ if (smallest > 600 || (width > 960 && height > 720) || (width > 720 && height > 960)) {
+ // For values-w600dp, values-sw600dp and values-xlarge.
+ return 5;
+ } else if (width >= 500 || (width > 640 && height > 480) || (width > 480 && height > 640)) {
+ // For values-w500dp and values-large.
+ return 4;
+ } else if (width >= 360) {
+ // For values-w360dp.
+ return 3;
+ } else {
+ return 2;
+ }
}
-
public boolean showsOverflowMenuButton() {
return true;
}
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 89d36ff..81056f2 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -25,7 +25,8 @@
oneway interface IInputMethodClient {
void setUsingInputMethod(boolean state);
void onBindMethod(in InputBindResult res);
- void onUnbindMethod(int sequence);
+ // unbindReason corresponds to InputMethodClient.UnbindReason.
+ void onUnbindMethod(int sequence, int unbindReason);
void setActive(boolean active);
void setUserActionNotificationSequenceNumber(int sequenceNumber);
}
diff --git a/core/java/com/android/internal/view/InputMethodClient.java b/core/java/com/android/internal/view/InputMethodClient.java
new file mode 100644
index 0000000..a035343
--- /dev/null
+++ b/core/java/com/android/internal/view/InputMethodClient.java
@@ -0,0 +1,60 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+public final class InputMethodClient {
+ public static final int UNBIND_REASON_UNSPECIFIED = 0;
+ public static final int UNBIND_REASON_SWITCH_CLIENT = 1;
+ public static final int UNBIND_REASON_SWITCH_IME = 2;
+ public static final int UNBIND_REASON_DISCONNECT_IME = 3;
+ public static final int UNBIND_REASON_NO_IME = 4;
+ public static final int UNBIND_REASON_SWITCH_IME_FAILED = 5;
+ public static final int UNBIND_REASON_RESET_IME = 6;
+
+ @Retention(SOURCE)
+ @IntDef({UNBIND_REASON_UNSPECIFIED, UNBIND_REASON_SWITCH_CLIENT, UNBIND_REASON_SWITCH_IME,
+ UNBIND_REASON_DISCONNECT_IME, UNBIND_REASON_NO_IME, UNBIND_REASON_SWITCH_IME_FAILED,
+ UNBIND_REASON_RESET_IME})
+ public @interface UnbindReason {}
+
+ public static String getUnbindReason(@UnbindReason final int reason) {
+ switch (reason) {
+ case UNBIND_REASON_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case UNBIND_REASON_SWITCH_CLIENT:
+ return "SWITCH_CLIENT";
+ case UNBIND_REASON_SWITCH_IME:
+ return "SWITCH_IME";
+ case UNBIND_REASON_DISCONNECT_IME:
+ return "DISCONNECT_IME";
+ case UNBIND_REASON_NO_IME:
+ return "NO_IME";
+ case UNBIND_REASON_SWITCH_IME_FAILED:
+ return "SWITCH_IME_FAILED";
+ case UNBIND_REASON_RESET_IME:
+ return "RESET_IME";
+ default:
+ return "Unknown=" + reason;
+ }
+ }
+}
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/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-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 94e9c4e..9c45c12 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -22,10 +22,6 @@
<dimen name="thumbnail_width">360dp</dimen>
<!-- The height that is used when creating thumbnails of applications. -->
<dimen name="thumbnail_height">360dp</dimen>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="max_action_buttons">5</integer>
<!-- Default height of an action bar. -->
<dimen name="action_bar_default_height">56dip</dimen>
<!-- Vertical padding around action bar icons. -->
@@ -88,7 +84,7 @@
<!-- Size of the generic status lines keyguard's status view -->
<dimen name="kg_status_line_font_size">16sp</dimen>
- <!-- Top margin for the clock view -->
+ <!-- Top margin for the clock view -->
<dimen name="kg_clock_top_margin">0dp</dimen>
<!-- Size of margin on the right of keyguard's status view -->
diff --git a/core/res/res/values-w360dp/dimens.xml b/core/res/res/values-w360dp/dimens.xml
deleted file mode 100644
index 0f5d656..0000000
--- a/core/res/res/values-w360dp/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="max_action_buttons">3</integer>
-</resources>
diff --git a/core/res/res/values-w500dp/dimens.xml b/core/res/res/values-w500dp/dimens.xml
deleted file mode 100644
index 68841ca..0000000
--- a/core/res/res/values-w500dp/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="max_action_buttons">4</integer>
-</resources>
diff --git a/core/res/res/values-w600dp/dimens.xml b/core/res/res/values-w600dp/dimens.xml
deleted file mode 100644
index 83c45b5..0000000
--- a/core/res/res/values-w600dp/dimens.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="max_action_buttons">5</integer>
-</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0f817f3..04f4fc2 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4248,7 +4248,8 @@
<attr name="autoLink" />
<!-- If set to false, keeps the movement method from being set
to the link movement method even if autoLink causes links
- to be found. -->
+ to be found or the input text contains a
+ {@link android.text.style.ClickableSpan ClickableSpan}. -->
<attr name="linksClickable" format="boolean" />
<!-- If set, specifies that this TextView has a numeric input method.
The default is false.
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 28756f5..01daf26 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -25,10 +25,7 @@
<!-- The standard size (both width and height) of an application icon that
will be displayed in the app launcher and elsewhere. -->
<dimen name="app_icon_size">48dip</dimen>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="max_action_buttons">2</integer>
+
<dimen name="toast_y_offset">64dip</dimen>
<!-- Height of the status bar -->
<dimen name="status_bar_height">24dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e8f6b46..7ce9d8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -401,7 +401,6 @@
<java-symbol type="integer" name="db_connection_pool_size" />
<java-symbol type="integer" name="db_journal_size_limit" />
<java-symbol type="integer" name="db_wal_autocheckpoint" />
- <java-symbol type="integer" name="max_action_buttons" />
<java-symbol type="integer" name="config_soundEffectVolumeDb" />
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
@@ -1958,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/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 1dc685c..f630055e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -1361,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;
@@ -1372,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() {
@@ -1385,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
@@ -1432,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);
}
}
@@ -1528,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);
}
}
@@ -1759,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/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 0ee970d..91ac033 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -358,18 +358,6 @@
return mState;
}
- public static abstract class DocumentsIntent {
- /** Intent action name to open copy destination. */
- public static String ACTION_OPEN_COPY_DESTINATION =
- "com.android.documentsui.OPEN_COPY_DESTINATION";
-
- /**
- * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
- * specifies if the destination directory needs to create new directory or not.
- */
- public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
- }
-
void setDisplayAdvancedDevices(boolean display) {
LocalPreferences.setDisplayAdvancedDevices(this, display);
mState.showAdvanced = mState.forceAdvanced | display;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55a123f..55e2f44 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -179,8 +179,7 @@
if (mFailedFiles.size() > 0) {
Log.e(TAG, mFailedFiles.size() + " files failed to copy");
final Context context = getApplicationContext();
- final Intent navigateIntent = new Intent(context, FilesActivity.class);
- navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+ final Intent navigateIntent = buildNavigateIntent(context, stack);
navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
@@ -228,8 +227,7 @@
mIsCancelled = false;
final Context context = getApplicationContext();
- final Intent navigateIntent = new Intent(context, FilesActivity.class);
- navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+ final Intent navigateIntent = buildNavigateIntent(context, stack);
final String contentTitle = getString(copying ? R.string.copy_notification_title
: R.string.move_notification_title);
@@ -592,4 +590,14 @@
}
}
}
+
+ /**
+ * Creates an intent for navigating back to the destination directory.
+ */
+ private Intent buildNavigateIntent(Context context, DocumentStack stack) {
+ Intent intent = new Intent(context, FilesActivity.class);
+ intent.setAction(DocumentsContract.ACTION_BROWSE);
+ intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+ return intent;
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 13c481c..e965050 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -19,7 +19,7 @@
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
import static com.android.documentsui.State.ACTION_OPEN;
-import static com.android.documentsui.State.ACTION_OPEN_COPY_DESTINATION;
+import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
import static com.android.documentsui.State.ACTION_OPEN_TREE;
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
@@ -123,7 +123,7 @@
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
SaveFragment.show(getFragmentManager(), mimeType, title);
} else if (mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
PickFragment.show(getFragmentManager());
}
@@ -135,7 +135,7 @@
} else if (mState.action == ACTION_OPEN ||
mState.action == ACTION_CREATE ||
mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
RootsFragment.show(getFragmentManager(), null);
}
@@ -163,8 +163,8 @@
state.action = ACTION_GET_CONTENT;
} else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
state.action = ACTION_OPEN_TREE;
- } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) {
- state.action = ACTION_OPEN_COPY_DESTINATION;
+ } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
+ state.action = ACTION_PICK_COPY_DESTINATION;
}
if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
@@ -172,9 +172,9 @@
Intent.EXTRA_ALLOW_MULTIPLE, false);
}
- if (state.action == ACTION_OPEN_COPY_DESTINATION) {
+ if (state.action == ACTION_PICK_COPY_DESTINATION) {
state.directoryCopy = intent.getBooleanExtra(
- BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
+ Shared.EXTRA_DIRECTORY_COPY, false);
state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
CopyService.TRANSFER_MODE_COPY);
}
@@ -257,7 +257,7 @@
mState.action == ACTION_OPEN_TREE) {
mRootsToolbar.setTitle(R.string.title_open);
} else if (mState.action == ACTION_CREATE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
mRootsToolbar.setTitle(R.string.title_save);
}
}
@@ -324,7 +324,7 @@
boolean recents = cwd == null;
boolean picking = mState.action == ACTION_CREATE
|| mState.action == ACTION_OPEN_TREE
- || mState.action == ACTION_OPEN_COPY_DESTINATION;
+ || mState.action == ACTION_PICK_COPY_DESTINATION;
createDir.setVisible(picking && !recents && cwd.isCreateSupported());
mSearchManager.showMenu(!picking);
@@ -361,7 +361,7 @@
// No directory means recents
if (mState.action == ACTION_CREATE ||
mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
RecentsCreateFragment.show(fm);
} else {
DirectoryFragment.showRecentsOpen(fm, anim);
@@ -391,7 +391,7 @@
}
if (mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
final PickFragment pick = PickFragment.get(fm);
if (pick != null) {
pick.setPickTarget(mState.action, mState.transferMode, cwd);
@@ -444,7 +444,7 @@
if (mState.action == ACTION_OPEN_TREE) {
result = DocumentsContract.buildTreeDocumentUri(
pickTarget.authority, pickTarget.documentId);
- } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
result = pickTarget.derivedUri;
} else {
// Should not be reached.
@@ -461,7 +461,7 @@
final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
if (mState.action == ACTION_CREATE ||
mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
// Remember stack for last create
values.clear();
values.put(RecentColumns.KEY, mState.stack.buildKey());
@@ -500,7 +500,7 @@
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
// Picking a copy destination is only used internally by us, so we
// don't need to extend permissions to the caller.
intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 48e28dc..bbf4682 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -110,7 +110,7 @@
mPick.setText(R.string.button_select);
mCancel.setVisibility(View.GONE);
break;
- case State.ACTION_OPEN_COPY_DESTINATION:
+ case State.ACTION_PICK_COPY_DESTINATION:
mPick.setText(R.string.button_copy);
mCancel.setVisibility(View.VISIBLE);
break;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 4fc3788..72ee6cbab 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -360,7 +360,7 @@
// Exclude read-only devices when creating
if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
- if (state.action == State.ACTION_OPEN_COPY_DESTINATION && !supportsCreate) continue;
+ if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue;
// Exclude roots that don't support directory picking
if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
// Exclude advanced devices when not requested
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index a4d6dc5..570c9bf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -20,6 +20,16 @@
/** @hide */
public final class Shared {
+ /** Intent action name to pick a copy destination. */
+ public static final String ACTION_PICK_COPY_DESTINATION =
+ "com.android.documentsui.PICK_COPY_DESTINATION";
+
+ /**
+ * Extra boolean flag for {@link ACTION_PICK_COPY_DESTINATION}, which
+ * specifies if the destination directory needs to create new directory or not.
+ */
+ public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
+
public static final boolean DEBUG = true;
public static final String TAG = "Documents";
public static final String EXTRA_STACK = "com.android.documentsui.STACK";
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 4306a0e..49a1e66 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -75,7 +75,7 @@
public static final int ACTION_OPEN_TREE = 4;
public static final int ACTION_MANAGE = 5;
public static final int ACTION_BROWSE = 6;
- public static final int ACTION_OPEN_COPY_DESTINATION = 8;
+ public static final int ACTION_PICK_COPY_DESTINATION = 8;
public static final int MODE_UNKNOWN = 0;
public static final int MODE_LIST = 1;
@@ -150,4 +150,4 @@
return new State[size];
}
};
-}
\ No newline at end of file
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 21420c8..18dd8c8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -111,8 +111,8 @@
import com.android.documentsui.State;
import com.android.documentsui.ThumbnailCache;
import com.android.documentsui.BaseActivity.DocumentContext;
-import com.android.documentsui.BaseActivity.DocumentsIntent;
import com.android.documentsui.ProviderExecutor.Preemptable;
+import com.android.documentsui.Shared;
import com.android.documentsui.RecentsProvider.StateColumns;
import com.android.documentsui.dirlist.MultiSelectManager.Callback;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
@@ -897,7 +897,7 @@
// Pop up a dialog to pick a destination. This is inadequate but works for now.
// TODO: Implement a picker that is to spec.
final Intent intent = new Intent(
- BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION,
+ Shared.ACTION_PICK_COPY_DESTINATION,
Uri.EMPTY,
getActivity(),
DocumentsActivity.class);
@@ -914,7 +914,7 @@
break;
}
}
- intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
+ intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode);
startActivityForResult(intent, REQUEST_COPY_DESTINATION);
}
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/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
new file mode 100644
index 0000000..ef511bb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accounts/AuthenticatorHelper.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.accounts;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SyncAdapterType;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper class for monitoring accounts on the device for a given user.
+ *
+ * Classes using this helper should implement {@link OnAccountsUpdateListener}.
+ * {@link OnAccountsUpdateListener#onAccountsUpdate(UserHandle)} will then be
+ * called once accounts get updated. For setting up listening for account
+ * updates, {@link #listenToAccountUpdates()} and
+ * {@link #stopListeningToAccountUpdates()} should be used.
+ */
+final public class AuthenticatorHelper extends BroadcastReceiver {
+ private static final String TAG = "AuthenticatorHelper";
+
+ private final Map<String, AuthenticatorDescription> mTypeToAuthDescription = new HashMap<>();
+ private final ArrayList<String> mEnabledAccountTypes = new ArrayList<>();
+ private final Map<String, Drawable> mAccTypeIconCache = new HashMap<>();
+ private final HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = new HashMap<>();
+
+ private final UserHandle mUserHandle;
+ private final Context mContext;
+ private final OnAccountsUpdateListener mListener;
+ private boolean mListeningToAccountUpdates;
+
+ public interface OnAccountsUpdateListener {
+ void onAccountsUpdate(UserHandle userHandle);
+ }
+
+ public AuthenticatorHelper(Context context, UserHandle userHandle,
+ OnAccountsUpdateListener listener) {
+ mContext = context;
+ mUserHandle = userHandle;
+ mListener = listener;
+ // This guarantees that the helper is ready to use once constructed: the account types and
+ // authorities are initialized
+ onAccountsUpdated(null);
+ }
+
+ public String[] getEnabledAccountTypes() {
+ return mEnabledAccountTypes.toArray(new String[mEnabledAccountTypes.size()]);
+ }
+
+ public void preloadDrawableForType(final Context context, final String accountType) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ getDrawableForType(context, accountType);
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ /**
+ * Gets an icon associated with a particular account type. If none found, return null.
+ * @param accountType the type of account
+ * @return a drawable for the icon or a default icon returned by
+ * {@link PackageManager#getDefaultActivityIcon} if one cannot be found.
+ */
+ public Drawable getDrawableForType(Context context, final String accountType) {
+ Drawable icon = null;
+ synchronized (mAccTypeIconCache) {
+ if (mAccTypeIconCache.containsKey(accountType)) {
+ return mAccTypeIconCache.get(accountType);
+ }
+ }
+ if (mTypeToAuthDescription.containsKey(accountType)) {
+ try {
+ AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+ Context authContext = context.createPackageContextAsUser(desc.packageName, 0,
+ mUserHandle);
+ icon = mContext.getPackageManager().getUserBadgedIcon(
+ authContext.getDrawable(desc.iconId), mUserHandle);
+ synchronized (mAccTypeIconCache) {
+ mAccTypeIconCache.put(accountType, icon);
+ }
+ } catch (PackageManager.NameNotFoundException|Resources.NotFoundException e) {
+ // Ignore
+ }
+ }
+ if (icon == null) {
+ icon = context.getPackageManager().getDefaultActivityIcon();
+ }
+ return icon;
+ }
+
+ /**
+ * Gets the label associated with a particular account type. If none found, return null.
+ * @param accountType the type of account
+ * @return a CharSequence for the label or null if one cannot be found.
+ */
+ public CharSequence getLabelForType(Context context, final String accountType) {
+ CharSequence label = null;
+ if (mTypeToAuthDescription.containsKey(accountType)) {
+ try {
+ AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+ Context authContext = context.createPackageContextAsUser(desc.packageName, 0,
+ mUserHandle);
+ label = authContext.getResources().getText(desc.labelId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "No label name for account type " + accountType);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "No label icon for account type " + accountType);
+ }
+ }
+ return label;
+ }
+
+ /**
+ * Gets the package associated with a particular account type. If none found, return null.
+ * @param accountType the type of account
+ * @return the package name or null if one cannot be found.
+ */
+ public String getPackageForType(final String accountType) {
+ if (mTypeToAuthDescription.containsKey(accountType)) {
+ AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+ return desc.packageName;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the resource id of the label associated with a particular account type. If none found,
+ * return -1.
+ * @param accountType the type of account
+ * @return a resource id for the label or -1 if none found;
+ */
+ public int getLabelIdForType(final String accountType) {
+ if (mTypeToAuthDescription.containsKey(accountType)) {
+ AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
+ return desc.labelId;
+ }
+ return -1;
+ }
+
+ /**
+ * Updates provider icons. Subclasses should call this in onCreate()
+ * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
+ */
+ public void updateAuthDescriptions(Context context) {
+ AuthenticatorDescription[] authDescs = AccountManager.get(context)
+ .getAuthenticatorTypesAsUser(mUserHandle.getIdentifier());
+ for (int i = 0; i < authDescs.length; i++) {
+ mTypeToAuthDescription.put(authDescs[i].type, authDescs[i]);
+ }
+ }
+
+ public boolean containsAccountType(String accountType) {
+ return mTypeToAuthDescription.containsKey(accountType);
+ }
+
+ public AuthenticatorDescription getAccountTypeDescription(String accountType) {
+ return mTypeToAuthDescription.get(accountType);
+ }
+
+ public boolean hasAccountPreferences(final String accountType) {
+ if (containsAccountType(accountType)) {
+ AuthenticatorDescription desc = getAccountTypeDescription(accountType);
+ if (desc != null && desc.accountPreferencesId != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void onAccountsUpdated(Account[] accounts) {
+ updateAuthDescriptions(mContext);
+ if (accounts == null) {
+ accounts = AccountManager.get(mContext).getAccountsAsUser(mUserHandle.getIdentifier());
+ }
+ mEnabledAccountTypes.clear();
+ mAccTypeIconCache.clear();
+ for (int i = 0; i < accounts.length; i++) {
+ final Account account = accounts[i];
+ if (!mEnabledAccountTypes.contains(account.type)) {
+ mEnabledAccountTypes.add(account.type);
+ }
+ }
+ buildAccountTypeToAuthoritiesMap();
+ if (mListeningToAccountUpdates) {
+ mListener.onAccountsUpdate(mUserHandle);
+ }
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // TODO: watch for package upgrades to invalidate cache; see http://b/7206643
+ final Account[] accounts = AccountManager.get(mContext)
+ .getAccountsAsUser(mUserHandle.getIdentifier());
+ onAccountsUpdated(accounts);
+ }
+
+ public void listenToAccountUpdates() {
+ if (!mListeningToAccountUpdates) {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
+ // At disk full, certain actions are blocked (such as writing the accounts to storage).
+ // It is useful to also listen for recovery from disk full to avoid bugs.
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ mContext.registerReceiverAsUser(this, mUserHandle, intentFilter, null, null);
+ mListeningToAccountUpdates = true;
+ }
+ }
+
+ public void stopListeningToAccountUpdates() {
+ if (mListeningToAccountUpdates) {
+ mContext.unregisterReceiver(this);
+ mListeningToAccountUpdates = false;
+ }
+ }
+
+ public ArrayList<String> getAuthoritiesForAccountType(String type) {
+ return mAccountTypeToAuthorities.get(type);
+ }
+
+ private void buildAccountTypeToAuthoritiesMap() {
+ mAccountTypeToAuthorities.clear();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+ mUserHandle.getIdentifier());
+ for (int i = 0, n = syncAdapters.length; i < n; i++) {
+ final SyncAdapterType sa = syncAdapters[i];
+ ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
+ if (authorities == null) {
+ authorities = new ArrayList<String>();
+ mAccountTypeToAuthorities.put(sa.accountType, authorities);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Added authority " + sa.authority + " to accountType "
+ + sa.accountType);
+ }
+ authorities.add(sa.authority);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java
new file mode 100644
index 0000000..f5e39be
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/AnimatedImageView.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedRotateDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+public class AnimatedImageView extends ImageView {
+ private AnimatedRotateDrawable mDrawable;
+ private boolean mAnimating;
+
+ public AnimatedImageView(Context context) {
+ super(context);
+ }
+
+ public AnimatedImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void updateDrawable() {
+ if (isShown() && mDrawable != null) {
+ mDrawable.stop();
+ }
+ final Drawable drawable = getDrawable();
+ if (drawable instanceof AnimatedRotateDrawable) {
+ mDrawable = (AnimatedRotateDrawable) drawable;
+ // TODO: define in drawable xml once we have public attrs.
+ mDrawable.setFramesCount(56);
+ mDrawable.setFramesDuration(32);
+ if (isShown() && mAnimating) {
+ mDrawable.start();
+ }
+ } else {
+ mDrawable = null;
+ }
+ }
+
+ private void updateAnimating() {
+ if (mDrawable != null) {
+ if (isShown() && mAnimating) {
+ mDrawable.start();
+ } else {
+ mDrawable.stop();
+ }
+ }
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ updateDrawable();
+ }
+
+ @Override
+ public void setImageResource(int resid) {
+ super.setImageResource(resid);
+ updateDrawable();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateAnimating();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ updateAnimating();
+ }
+
+ public void setAnimating(boolean animating) {
+ mAnimating = animating;
+ updateAnimating();
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int vis) {
+ super.onVisibilityChanged(changedView, vis);
+ updateAnimating();
+ }
+}
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 3db0848..4469d38 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -33,4 +33,8 @@
<!-- Title for documents backend that offers bugreports. -->
<string name="bugreport_storage_title">Bug reports</string>
+
+ <!-- Toast message sent when the bugreport file could be read. -->
+ <string name="bugreport_unreadable_text">Bug report file could not be read</string>
+
</resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index e90a3b5..c264372 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -37,6 +37,7 @@
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Patterns;
+import android.widget.Toast;
import com.google.android.collect.Lists;
import libcore.io.Streams;
@@ -105,6 +106,13 @@
*/
private void triggerLocalNotification(final Context context, final File bugreportFile,
final File screenshotFile) {
+ if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+ Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
if (!isPlainText) {
// Already zipped, send it right away.
@@ -141,10 +149,12 @@
intent.putExtra(Intent.EXTRA_TEXT, messageBody);
final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, bugreportUri));
- clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+ if (screenshotUri != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ attachments.add(screenshotUri);
+ }
intent.setClipData(clipData);
-
- final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
final Account sendToAccount = findSendToAccount(context);
@@ -162,8 +172,8 @@
File screenshotFile) {
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
- final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
- final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+ final Uri bugreportUri = getUri(context, bugreportFile);
+ final Uri screenshotUri = getUri(context, screenshotFile);
Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
Intent notifIntent;
@@ -272,6 +282,10 @@
return foundAccount;
}
+ private static Uri getUri(Context context, File file) {
+ return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+ }
+
private static File getFileExtra(Intent intent, String key) {
final String path = intent.getStringExtra(key);
if (path != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index b4bb392..4e24d54 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -421,6 +421,8 @@
if (state.startHidden) {
state.startHidden = false;
mRecentsView.setStackViewVisibility(View.INVISIBLE);
+ } else {
+ mRecentsView.setStackViewVisibility(View.VISIBLE);
}
}
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..66ece52 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -31,7 +31,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;
@@ -93,7 +92,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;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9e3cf37..e6a291c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -594,7 +594,9 @@
// Setup the animation with the screenshot just taken
if (mScreenshotAnimation != null) {
- mScreenshotAnimation.end();
+ if (mScreenshotAnimation.isStarted()) {
+ mScreenshotAnimation.end();
+ }
mScreenshotAnimation.removeAllListeners();
}
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/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 4328e24..83dbde5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -105,36 +105,29 @@
}
public boolean cacheContentViews(Context ctx, Notification updatedNotification) {
- boolean cached = false;
+ boolean applyInPlace = false;
if (updatedNotification != null) {
final Notification.Builder updatedNotificationBuilder
= Notification.Builder.recoverBuilder(ctx, updatedNotification);
final RemoteViews newContentView = updatedNotificationBuilder.makeContentView();
- if (!compareRemoteViews(cachedContentView, newContentView)) {
- cachedContentView = newContentView;
- cached |= true;
- }
final RemoteViews newBigContentView =
updatedNotificationBuilder.makeBigContentView();
- if (!compareRemoteViews(cachedBigContentView, newBigContentView)) {
- cachedBigContentView = newBigContentView;
- cached |= true;
- }
final RemoteViews newHeadsUpContentView =
updatedNotificationBuilder.makeHeadsUpContentView();
- if (!compareRemoteViews(cachedHeadsUpContentView, newBigContentView)) {
- cachedHeadsUpContentView = newHeadsUpContentView;
- cached |= true;
- }
final Notification updatedPublicNotification = updatedNotification.publicVersion;
final RemoteViews newPubContentView = (updatedPublicNotification != null)
? Notification.Builder.recoverBuilder(
ctx, updatedPublicNotification).makeContentView()
: null;
- if (!compareRemoteViews(cachedPublicContentView, newPubContentView)) {
- cachedPublicContentView = newPubContentView;
- cached |= true;
- }
+
+ applyInPlace = compareRemoteViews(cachedContentView, newContentView)
+ && compareRemoteViews(cachedBigContentView, newBigContentView)
+ && compareRemoteViews(cachedHeadsUpContentView, newHeadsUpContentView)
+ && compareRemoteViews(cachedPublicContentView, newPubContentView);
+ cachedPublicContentView = newPubContentView;
+ cachedHeadsUpContentView = newHeadsUpContentView;
+ cachedBigContentView = newBigContentView;
+ cachedContentView = newContentView;
} else {
final Notification.Builder builder
= Notification.Builder.recoverBuilder(ctx, notification.getNotification());
@@ -150,9 +143,9 @@
= Notification.Builder.recoverBuilder(ctx, publicNotification);
cachedPublicContentView = publicBuilder.makeContentView();
}
- cached = true;
+ applyInPlace = false;
}
- return cached;
+ return applyInPlace;
}
// Returns true if the RemoteViews are the same.
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index d404a42..40f7c5c 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -30,6 +30,7 @@
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.IInputSessionCallback;
import com.android.internal.view.InputBindResult;
+import com.android.internal.view.InputMethodClient;
import com.android.server.statusbar.StatusBarManagerService;
import org.xmlpull.v1.XmlPullParser;
@@ -160,8 +161,8 @@
static final int MSG_START_INPUT = 2000;
static final int MSG_RESTART_INPUT = 2010;
- static final int MSG_UNBIND_METHOD = 3000;
- static final int MSG_BIND_METHOD = 3010;
+ static final int MSG_UNBIND_CLIENT = 3000;
+ static final int MSG_BIND_CLIENT = 3010;
static final int MSG_SET_ACTIVE = 3020;
static final int MSG_SET_INTERACTIVE = 3030;
static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
@@ -935,7 +936,7 @@
|| (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
if (!updateOnlyWhenLocaleChanged) {
hideCurrentInputLocked(0, null);
- resetCurrentMethodAndClient();
+ resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_RESET_IME);
}
if (DEBUG) {
Slog.i(TAG, "Locale has been changed to " + newLocale);
@@ -1195,6 +1196,9 @@
ClientState cs = mClients.remove(client.asBinder());
if (cs != null) {
clearClientSessionLocked(cs);
+ if (mCurClient == cs) {
+ mCurClient = null;
+ }
}
}
}
@@ -1208,7 +1212,8 @@
}
}
- void unbindCurrentClientLocked() {
+ void unbindCurrentClientLocked(
+ /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
if (mCurClient != null) {
if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
+ mCurClient.client.asBinder());
@@ -1222,8 +1227,8 @@
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_SET_ACTIVE, 0, mCurClient));
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+ executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+ MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
mCurClient.sessionRequested = false;
mCurClient = null;
@@ -1324,7 +1329,7 @@
mCurClientInKeyguard = isKeyguardLocked();
// If the client is changing, we need to switch over to the new
// one.
- unbindCurrentClientLocked();
+ unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
if (DEBUG) Slog.v(TAG, "switching to client: client = "
+ cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
@@ -1479,7 +1484,7 @@
InputBindResult res = attachNewInputLocked(true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
- MSG_BIND_METHOD, mCurClient.client, res));
+ MSG_BIND_CLIENT, mCurClient.client, res));
}
return;
}
@@ -1518,10 +1523,11 @@
clearCurMethodLocked();
}
- void resetCurrentMethodAndClient() {
+ void resetCurrentMethodAndClient(
+ /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
mCurMethodId = null;
unbindCurrentMethodLocked(false);
- unbindCurrentClientLocked();
+ unbindCurrentClientLocked(unbindClientReason);
}
void requestClientSessionLocked(ClientState cs) {
@@ -1588,8 +1594,9 @@
mShowRequested = mInputShown;
mInputShown = false;
if (mCurClient != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+ executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
+ MSG_UNBIND_CLIENT, InputMethodClient.UNBIND_REASON_DISCONNECT_IME,
+ mCurSeq, mCurClient.client));
}
}
}
@@ -1874,12 +1881,12 @@
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
- resetCurrentMethodAndClient();
+ resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
- resetCurrentMethodAndClient();
+ resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
}
// Here is not the perfect place to reset the switching controller. Ideally
// mSwitchingController and mSettings should be able to share the same state.
@@ -1965,7 +1972,7 @@
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
- unbindCurrentClientLocked();
+ unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2769,14 +2776,14 @@
// ---------------------------------------------------------
- case MSG_UNBIND_METHOD:
+ case MSG_UNBIND_CLIENT:
try {
- ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
+ ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
} catch (RemoteException e) {
// There is nothing interesting about the last client dying.
}
return true;
- case MSG_BIND_METHOD: {
+ case MSG_BIND_CLIENT: {
args = (SomeArgs)msg.obj;
IInputMethodClient client = (IInputMethodClient)args.arg1;
InputBindResult res = (InputBindResult)args.arg2;
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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4d05f9a..17b3d2a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1094,15 +1094,20 @@
}
r = smap.mServicesByName.get(name);
if (r == null && createIfNeeded) {
- // Before going further -- if this app is not allowed to run in the background,
- // then at this point we aren't going to let it period.
- if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
- sInfo.packageName, callingPid)) {
- Slog.w(TAG, "Background execution not allowed: service "
- + r.intent + " to " + name.flattenToShortString()
- + " from pid=" + callingPid + " uid=" + callingUid
- + " pkg=" + callingPackage);
- return null;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Before going further -- if this app is not allowed to run in the
+ // background, then at this point we aren't going to let it period.
+ if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+ sInfo.packageName, callingPid)) {
+ Slog.w(TAG, "Background execution not allowed: service "
+ + r.intent + " to " + name.flattenToShortString()
+ + " from pid=" + callingPid + " uid=" + callingUid
+ + " pkg=" + callingPackage);
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
Intent.FilterComparison filter
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e6bfb7..92e16c7 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);
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..79aa85f 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;
@@ -1926,9 +1925,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 +2929,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 +2954,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/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/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ab0b182..53ce9e2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -322,7 +322,7 @@
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
- if (ui.isPrimary()) {
+ if (ui.isPrimary() && !mRemovingUserIds.get(ui.id)) {
return ui;
}
}
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/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1caeca0..b85a6923 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -401,6 +401,32 @@
}
}
+ public void cancelDrag(IBinder dragToken) {
+ if (WindowManagerService.DEBUG_DRAG) {
+ Slog.d(WindowManagerService.TAG, "cancel drag");
+ }
+
+ synchronized (mService.mWindowMap) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mService.mDragState == null) {
+ Slog.w(WindowManagerService.TAG, "cancelDrag() without prepareDrag()");
+ throw new IllegalStateException("cancelDrag() without prepareDrag()");
+ }
+
+ if (mService.mDragState.mToken != dragToken) {
+ Slog.w(WindowManagerService.TAG, "cancelDrag() does not match prepareDrag()");
+ throw new IllegalStateException("cancelDrag() does not match prepareDrag()");
+ }
+
+ mService.mDragState.mDragResult = false;
+ mService.mDragState.endDragLw();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
public void dragRecipientEntered(IWindow window) {
if (WindowManagerService.DEBUG_DRAG) {
Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index f396c2d..8fee91f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -362,7 +362,6 @@
}
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Failure looking up interaction service " + comp);
- } catch (RemoteException e) {
}
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 30296e1..109d214 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -116,7 +116,7 @@
VoiceInteractionServiceInfo info;
try {
info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
- } catch (RemoteException|PackageManager.NameNotFoundException e) {
+ } catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Voice interaction service not found: " + service, e);
mInfo = null;
mSessionComponentName = null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 1ec0547..4436a40 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -167,6 +167,11 @@
}
@Override
+ public void cancelDrag(IBinder dragToken) {
+ // pass for now
+ }
+
+ @Override
public void dragRecipientEntered(IWindow window) throws RemoteException {
// pass for now
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 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;
+ }
+}