Merge "Clean up USER_OWNER in TrustManagerService"
diff --git a/api/current.txt b/api/current.txt
index bdab385..d468831 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3431,6 +3431,7 @@
method public boolean onSearchRequested(android.view.SearchEvent);
method public boolean onSearchRequested();
method protected void onStart();
+ method public void onStateNotSaved();
method protected void onStop();
method protected void onTitleChanged(java.lang.CharSequence, int);
method public boolean onTouchEvent(android.view.MotionEvent);
@@ -30114,7 +30115,6 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
method public final void setConnectionCapabilities(int);
- method public final void setConnectionService(android.telecom.ConnectionService);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
method public final void setExtras(android.os.Bundle);
@@ -35102,9 +35102,13 @@
field public static final int KEYCODE_DEL = 67; // 0x43
field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
+ field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
+ field public static final int KEYCODE_DPAD_DOWN_RIGHT = 271; // 0x10f
field public static final int KEYCODE_DPAD_LEFT = 21; // 0x15
field public static final int KEYCODE_DPAD_RIGHT = 22; // 0x16
field public static final int KEYCODE_DPAD_UP = 19; // 0x13
+ field public static final int KEYCODE_DPAD_UP_LEFT = 268; // 0x10c
+ field public static final int KEYCODE_DPAD_UP_RIGHT = 270; // 0x10e
field public static final int KEYCODE_DVR = 173; // 0xad
field public static final int KEYCODE_E = 33; // 0x21
field public static final int KEYCODE_EISU = 212; // 0xd4
diff --git a/api/system-current.txt b/api/system-current.txt
index 1025a71..4dd85e6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3535,6 +3535,7 @@
method public boolean onSearchRequested(android.view.SearchEvent);
method public boolean onSearchRequested();
method protected void onStart();
+ method public void onStateNotSaved();
method protected void onStop();
method protected void onTitleChanged(java.lang.CharSequence, int);
method public boolean onTouchEvent(android.view.MotionEvent);
@@ -32316,7 +32317,6 @@
method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
method public final void setConferenceables(java.util.List<android.telecom.Conferenceable>);
method public final void setConnectionCapabilities(int);
- method public final void setConnectionService(android.telecom.ConnectionService);
method public final void setDialing();
method public final void setDisconnected(android.telecom.DisconnectCause);
method public final void setExtras(android.os.Bundle);
@@ -37413,9 +37413,13 @@
field public static final int KEYCODE_DEL = 67; // 0x43
field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
+ field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
+ field public static final int KEYCODE_DPAD_DOWN_RIGHT = 271; // 0x10f
field public static final int KEYCODE_DPAD_LEFT = 21; // 0x15
field public static final int KEYCODE_DPAD_RIGHT = 22; // 0x16
field public static final int KEYCODE_DPAD_UP = 19; // 0x13
+ field public static final int KEYCODE_DPAD_UP_LEFT = 268; // 0x10c
+ field public static final int KEYCODE_DPAD_UP_RIGHT = 270; // 0x10e
field public static final int KEYCODE_DVR = 173; // 0xad
field public static final int KEYCODE_E = 33; // 0x21
field public static final int KEYCODE_EISU = 212; // 0xd4
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 3e4a66d..9c401c7f 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -138,7 +138,9 @@
new AccountAuthenticatorResponse(response),
accountType, authTokenType, features, options);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- result.keySet(); // force it to be unparcelled
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
}
if (result != null) {
@@ -160,7 +162,9 @@
final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials(
new AccountAuthenticatorResponse(response), account, options);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- result.keySet(); // force it to be unparcelled
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
Log.v(TAG, "confirmCredentials: result "
+ AccountManager.sanitizeResult(result));
}
@@ -185,7 +189,9 @@
result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL,
AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- result.keySet(); // force it to be unparcelled
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
Log.v(TAG, "getAuthTokenLabel: result "
+ AccountManager.sanitizeResult(result));
}
@@ -209,7 +215,9 @@
new AccountAuthenticatorResponse(response), account,
authTokenType, loginOptions);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- result.keySet(); // force it to be unparcelled
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result));
}
if (result != null) {
@@ -234,7 +242,10 @@
new AccountAuthenticatorResponse(response), account,
authTokenType, loginOptions);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- result.keySet(); // force it to be unparcelled
+ // Result may be null.
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
Log.v(TAG, "updateCredentials: result "
+ AccountManager.sanitizeResult(result));
}
@@ -490,7 +501,7 @@
* <ul>
* <li> {@link AccountManager#KEY_INTENT}, or
* <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
- * the account that was added, or
+ * the account whose credentials were updated, or
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
* indicate an error
* </ul>
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 9394d2c..8c84b4d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -333,7 +333,7 @@
try {
return mService.getPassword(account);
} catch (RemoteException e) {
- // will never happen
+ // won't ever happen
throw new RuntimeException(e);
}
}
@@ -362,7 +362,7 @@
try {
return mService.getUserData(account, key);
} catch (RemoteException e) {
- // will never happen
+ // won't ever happen
throw new RuntimeException(e);
}
}
@@ -415,8 +415,10 @@
*
* <p>It is safe to call this method from the main thread.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
* @return An array of {@link Account}, one for each account. Empty
* (never null) if no accounts have been added.
@@ -438,8 +440,10 @@
*
* <p>It is safe to call this method from the main thread.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
* @return An array of {@link Account}, one for each account. Empty
* (never null) if no accounts have been added.
@@ -466,7 +470,7 @@
try {
return mService.getAccountsForPackage(packageName, uid);
} catch (RemoteException re) {
- // possible security exception
+ // won't ever happen
throw new RuntimeException(re);
}
}
@@ -483,7 +487,7 @@
try {
return mService.getAccountsByTypeForPackage(type, packageName);
} catch (RemoteException re) {
- // possible security exception
+ // won't ever happen
throw new RuntimeException(re);
}
}
@@ -497,9 +501,10 @@
*
* <p>It is safe to call this method from the main thread.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
- * authenticator that owns the account type.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
* <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
* GET_ACCOUNTS permission is needed for those platforms, irrespective of uid
@@ -585,7 +590,8 @@
* {@link AccountManagerFuture} must not be used on the main thread.
*
* <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ * {@link android.Manifest.permission#GET_ACCOUNTS} or be a signature
+ * match with the AbstractAccountAuthenticator that manages the account.
*
* @param account The {@link Account} to test
* @param features An array of the account features to check
@@ -628,9 +634,10 @@
* <p>This method may be called from any thread, but the returned
* {@link AccountManagerFuture} must not be used on the main thread.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
- * authenticator that owns the account type.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
* @param type The type of accounts to return, must not be null
* @param features An array of the account features to require,
@@ -701,7 +708,7 @@
try {
return mService.addAccountExplicitly(account, password, userdata);
} catch (RemoteException e) {
- // won't ever happen
+ // Can happen if there was a SecurityException was thrown.
throw new RuntimeException(e);
}
}
@@ -966,7 +973,7 @@
try {
return mService.removeAccountExplicitly(account);
} catch (RemoteException e) {
- // won't ever happen
+ // May happen if the caller doesn't match the signature of the authenticator.
throw new RuntimeException(e);
}
}
@@ -1114,7 +1121,7 @@
try {
mService.setUserData(account, key, value);
} catch (RemoteException e) {
- // won't ever happen
+ // Will happen if there is not signature match.
throw new RuntimeException(e);
}
}
@@ -1733,7 +1740,7 @@
* with these fields if an activity was supplied and the account
* credentials were successfully updated:
* <ul>
- * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
* <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
* </ul>
*
@@ -2501,10 +2508,12 @@
* listeners are added in an Activity or Service's {@link Activity#onCreate}
* and removed in {@link Activity#onDestroy}.
*
- * <p>It is safe to call this method from the main thread.
+ * <p>The listener will only be informed of accounts that would be returned
+ * to the caller via {@link #getAccounts()}. Typically this means that to
+ * get any accounts, the caller will need to be grated the GET_ACCOUNTS
+ * permission.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ * <p>It is safe to call this method from the main thread.
*
* @param listener The listener to send notifications to
* @param handler {@link Handler} identifying the thread to use
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 5480193..f7a7403 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -228,7 +228,9 @@
int size = mNodes.size();
for (int i = 0; i < size; i++) {
Node node = mNodes.get(i);
- childList.add(node.mAnimation);
+ if (node != mRootNode) {
+ childList.add(node.mAnimation);
+ }
}
return childList;
}
@@ -702,7 +704,7 @@
}
- private class AnimatorSetListener implements AnimatorListener {
+ private static class AnimatorSetListener implements AnimatorListener {
private AnimatorSet mAnimatorSet;
@@ -712,7 +714,7 @@
public void onAnimationCancel(Animator animation) {
- if (!mTerminated) {
+ if (!mAnimatorSet.mTerminated) {
// Listeners are already notified of the AnimatorSet canceling in cancel().
// The logic below only kicks in when animations end normally
if (mAnimatorSet.mPlayingSet.size() == 0) {
@@ -734,7 +736,7 @@
Node animNode = mAnimatorSet.mNodeMap.get(animation);
animNode.mEnded = true;
- if (!mTerminated) {
+ if (!mAnimatorSet.mTerminated) {
List<Node> children = animNode.mChildNodes;
// Start children animations, if any.
int childrenSize = children == null ? 0 : children.size();
@@ -747,9 +749,9 @@
// end(); the logic below only kicks in when animations end normally
boolean allDone = true;
// Traverse the tree and find if there's any unfinished node
- int size = mNodes.size();
+ int size = mAnimatorSet.mNodes.size();
for (int i = 0; i < size; i++) {
- if (!mNodes.get(i).mEnded) {
+ if (!mAnimatorSet.mNodes.get(i).mEnded) {
allDone = false;
break;
}
@@ -757,9 +759,9 @@
if (allDone) {
// If this was the last child animation to end, then notify listeners that this
// AnimatorSet has ended
- if (mListeners != null) {
+ if (mAnimatorSet.mListeners != null) {
ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
+ (ArrayList<AnimatorListener>) mAnimatorSet.mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationEnd(mAnimatorSet);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2f40c18..983af2f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -31,6 +31,7 @@
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
+import android.view.Window.WindowStackCallback;
import android.widget.Toolbar;
import com.android.internal.app.IVoiceInteractor;
@@ -672,7 +673,7 @@
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
- Window.OnWindowDismissedCallback {
+ Window.OnWindowDismissedCallback, WindowStackCallback {
private static final String TAG = "Activity";
private static final boolean DEBUG_LIFECYCLE = false;
@@ -683,6 +684,13 @@
/** Start of user-defined activity results. */
public static final int RESULT_FIRST_USER = 1;
+ /** @hide Task isn't finished when activity is finished */
+ public static final int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
+ /** @hide Task is finished if the finishing activity is the root of the task */
+ public static final int FINISH_TASK_WITH_ROOT_ACTIVITY = 1;
+ /** @hide Task is finished along with the finishing activity*/
+ public static final int FINISH_TASK_WITH_ACTIVITY = 2;
+
static final String FRAGMENTS_TAG = "android:fragments";
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
@@ -1173,6 +1181,16 @@
}
/**
+ * Called when an {@link #onResume} is coming up, prior to other pre-resume callbacks
+ * such as {@link #onNewIntent} and {@link #onActivityResult}. This is primarily intended
+ * to give the activity a hint that its state is no longer saved -- it will generally
+ * be called after {@link #onSaveInstanceState} and prior to the activity being
+ * resumed/started again.
+ */
+ public void onStateNotSaved() {
+ }
+
+ /**
* Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
* {@link #onPause}, for your activity to start interacting with the user.
* This is a good place to begin animations, open exclusive-access devices
@@ -2689,8 +2707,30 @@
* @hide
*/
@Override
- public void onWindowDismissed() {
- finish();
+ public void onWindowDismissed(boolean finishTask) {
+ finish(finishTask ? FINISH_TASK_WITH_ACTIVITY : DONT_FINISH_TASK_WITH_ACTIVITY);
+ }
+
+
+ /** Called to move the window and its activity/task to a different stack container.
+ * For example, a window can move between
+ * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+ * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+ *
+ * @param stackId stack Id to change to.
+ * @hide
+ */
+ @Override
+ public void changeWindowStack(int stackId) throws RemoteException {
+ ActivityManagerNative.getDefault().moveActivityToStack(mToken, stackId);
+ }
+
+ /** Returns the current stack Id for the window.
+ * @hide
+ */
+ @Override
+ public int getWindowStackId() throws RemoteException {
+ return ActivityManagerNative.getDefault().getActivityStackId(mToken);
}
/**
@@ -4838,7 +4878,7 @@
* Finishes the current activity and specifies whether to remove the task associated with this
* activity.
*/
- private void finish(boolean finishTask) {
+ private void finish(int finishTask) {
if (mParent == null) {
int resultCode;
Intent resultData;
@@ -4869,7 +4909,7 @@
* onActivityResult().
*/
public void finish() {
- finish(false);
+ finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}
/**
@@ -4969,10 +5009,10 @@
/**
* Call this when your activity is done and should be closed and the task should be completely
- * removed as a part of finishing the Activity.
+ * removed as a part of finishing the root activity of the task.
*/
public void finishAndRemoveTask() {
- finish(true);
+ finish(FINISH_TASK_WITH_ROOT_ACTIVITY);
}
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b7a7d07..78e1b76 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -405,7 +405,7 @@
public static final int COMPAT_MODE_TOGGLE = 2;
/**
- * First static stack stack ID.
+ * First static stack ID.
* @hide
*/
public static final int FIRST_STATIC_STACK_ID = 0;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e9d4113..4232db4 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -346,7 +346,7 @@
if (data.readInt() != 0) {
resultData = Intent.CREATOR.createFromParcel(data);
}
- boolean finishTask = (data.readInt() != 0);
+ int finishTask = data.readInt();
boolean res = finishActivity(token, resultCode, resultData, finishTask);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
@@ -2636,6 +2636,22 @@
reply.writeInt(res ? 1 : 0);
return true;
}
+ case GET_ACTIVITY_STACK_ID_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ int stackId = getActivityStackId(token);
+ reply.writeNoException();
+ reply.writeInt(stackId);
+ return true;
+ }
+ case MOVE_ACTIVITY_TO_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ int stackId = data.readInt();
+ moveActivityToStack(token, stackId);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -2949,7 +2965,7 @@
data.recycle();
return result;
}
- public boolean finishActivity(IBinder token, int resultCode, Intent resultData, boolean finishTask)
+ public boolean finishActivity(IBinder token, int resultCode, Intent resultData, int finishTask)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2962,7 +2978,7 @@
} else {
data.writeInt(0);
}
- data.writeInt(finishTask ? 1 : 0);
+ data.writeInt(finishTask);
mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -6101,5 +6117,32 @@
return res != 0;
}
+ @Override
+ public void moveActivityToStack(IBinder token, int stackId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ data.writeInt(stackId);
+ mRemote.transact(MOVE_ACTIVITY_TO_STACK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public int getActivityStackId(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(GET_ACTIVITY_STACK_ID_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int stackId = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return stackId;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4474c75..594ec41 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2555,7 +2555,8 @@
// manager to stop us.
try {
ActivityManagerNative.getDefault()
- .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
+ .finishActivity(r.token, Activity.RESULT_CANCELED, null,
+ Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
// Ignore
}
@@ -3120,6 +3121,7 @@
r.activity.mStartedActivity = false;
}
try {
+ r.activity.onStateNotSaved();
r.activity.mFragments.noteStateNotSaved();
if (r.pendingIntents != null) {
deliverNewIntents(r, r.pendingIntents);
@@ -3280,7 +3282,8 @@
// just end this activity.
try {
ActivityManagerNative.getDefault()
- .finishActivity(token, Activity.RESULT_CANCELED, null, false);
+ .finishActivity(token, Activity.RESULT_CANCELED, null,
+ Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index f6e0e1e..6e8e2c4 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -721,29 +721,29 @@
public void onContentChanged() {
}
-
+
public void onWindowFocusChanged(boolean hasFocus) {
}
public void onAttachedToWindow() {
}
-
+
public void onDetachedFromWindow() {
}
/** @hide */
@Override
- public void onWindowDismissed() {
+ public void onWindowDismissed(boolean finishTask) {
dismiss();
}
-
+
/**
- * Called to process key events. You can override this to intercept all
- * key events before they are dispatched to the window. Be sure to call
+ * Called to process key events. You can override this to intercept all
+ * key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
- *
+ *
* @param event The key event.
- *
+ *
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 5352465..00e397d 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -93,7 +93,7 @@
public boolean startNextMatchingActivity(IBinder callingActivity,
Intent intent, Bundle options) throws RemoteException;
public int startActivityFromRecents(int taskId, Bundle options) throws RemoteException;
- public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
+ public boolean finishActivity(IBinder token, int code, Intent data, int finishTask)
throws RemoteException;
public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
public boolean finishActivityAffinity(IBinder token) throws RemoteException;
@@ -527,6 +527,9 @@
// descriptor.
public boolean stopBinderTrackingAndDump(ParcelFileDescriptor fd) throws RemoteException;
+ public int getActivityStackId(IBinder token) throws RemoteException;
+ public void moveActivityToStack(IBinder token, int stackId) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -879,4 +882,6 @@
int START_BINDER_TRACKING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 340;
int STOP_BINDER_TRACKING_AND_DUMP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 341;
int POSITION_TASK_IN_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 342;
+ int GET_ACTIVITY_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 343;
+ int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f21422e..c505b0b 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -124,6 +124,12 @@
*
* <p> If provisioning fails, the managedProfile is removed so the device returns to its
* previous state.
+ *
+ * <p>If launched with {@link android.app.Activity#startActivityForResult(Intent, int)} a
+ * result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part of
+ * the provisioning flow was successful, although this doesn't guarantee the full flow will
+ * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
+ * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROVISION_MANAGED_PROFILE
@@ -158,6 +164,10 @@
*
* <p> If provisioning fails, the device is factory reset.
*
+ * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
+ * of the provisioning flow was successful, although this doesn't guarantee the full flow will
+ * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
+ * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROVISION_MANAGED_DEVICE
@@ -165,13 +175,19 @@
/**
* A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
- * allows a mobile device management application which starts managed provisioning to pass data
- * to itself.
+ * allows a mobile device management application or NFC programmer application which starts
+ * managed provisioning to pass data to the management application instance after provisioning.
* <p>
* If used with {@link #ACTION_PROVISION_MANAGED_PROFILE} it can be used by the application that
* sends the intent to pass data to itself on the newly created profile.
* If used with {@link #ACTION_PROVISION_MANAGED_DEVICE} it allows passing data to the same
* instance of the app on the primary user.
+ * Starting from {@link android.os.Build.VERSION_CODES#M}, if used with
+ * {@link #MIME_TYPE_PROVISIONING_NFC} as part of NFC managed device provisioning, the NFC
+ * message should contain a stringified {@link java.util.Properties} instance, whose string
+ * properties will be converted into a {@link android.os.PersistableBundle} and passed to the
+ * management application after provisioning.
+ *
* <p>
* In both cases the application receives the data in
* {@link DeviceAdminReceiver#onProfileProvisioningComplete} via an intent with the action
@@ -587,7 +603,9 @@
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li>
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li></ul>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional, supported from
+ * {@link android.os.Build.VERSION_CODES#M} </li></ul>
*
* <p>
* As of {@link android.os.Build.VERSION_CODES#M}, the properties should contain
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 97afafa..1f3ff51 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1002,6 +1002,25 @@
}
/**
+ * Factory reset bluetooth settings.
+ *
+ * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
+ * permission
+ *
+ * @return true to indicate that the config file was successfully cleared
+ *
+ * @hide
+ */
+ public boolean factoryReset() {
+ try {
+ if (mService != null) {
+ return mService.factoryReset();
+ }
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
* Get the UUIDs supported by the local Bluetooth adapter.
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 7a894ae..66f3418 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -92,6 +92,7 @@
ParcelFileDescriptor createSocketChannel(int type, in String serviceName, in ParcelUuid uuid, int port, int flag);
boolean configHciSnoopLog(boolean enable);
+ boolean factoryReset();
boolean isMultiAdvertisementSupported();
boolean isPeripheralModeSupported();
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index f14d965..19b452f 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -43,7 +43,6 @@
*/
@Override
public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
- vibrate(milliseconds);
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f2bf6dc..601403c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7567,6 +7567,13 @@
}
/**
+ * Value of the ringer before entering zen mode.
+ *
+ * @hide
+ */
+ public static final String ZEN_MODE_RINGER_LEVEL = "zen_mode_ringer_level";
+
+ /**
* Opaque value, changes when persisted zen mode configuration changes.
*
* @hide
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index fa015b2..c3aed75 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -40,6 +40,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
@@ -247,7 +248,7 @@
* @return A list of features supported for the given language.
*/
protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
- return null;
+ return new HashSet<String>();
}
private int getExpectedLanguageAvailableStatus(Locale locale) {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 5994d4f..0a4b982 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -80,10 +80,37 @@
void setEventDispatching(boolean enabled);
void addWindowToken(IBinder token, int type);
void removeWindowToken(IBinder token);
- void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
+ /**
+ * Adds an application token to the specified task Id.
+ * @param addPos The position to add the token to in the task.
+ * @param token The token to add.
+ * @param taskId The Id of the task we are adding the token to.
+ * @param stackId Stack Id to create a new Task with the input task Id on
+ * if the task doesn't exist yet.
+ * @param requestedOrientation Orientation to use.
+ * @param fullscreen True if the application token is fullscreen.
+ * @param showWhenLocked True if the application token should be shown when locked.
+ * @param userId Id of user to associate the token with.
+ * @param configChanges Input configuration changes.
+ * @param voiceInteraction True if the token is in voice interaction mode.
+ * @param launchTaskBehind True if the token is been launched from behind.
+ * @param taskBounds Bounds to use when creating a new Task with the input task Id if
+ * the task doesn't exist yet.
+ * @return The configuration of the task if it was newly created. null otherwise.
+ */
+ Configuration addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
- int configChanges, boolean voiceInteraction, boolean launchTaskBehind);
- void setAppTask(IBinder token, int taskId);
+ int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
+ in Rect taskBounds);
+ /**
+ *
+ * @param token The token we are adding to the input task Id.
+ * @param taskId The Id of the task we are adding the token to.
+ * @param taskBounds Bounds to use when creating a new Task with the input task Id if
+ * the task doesn't exist yet.
+ * @return The configuration of the task if it was newly created. null otherwise.
+ */
+ Configuration setAppTask(IBinder token, int taskId, in Rect taskBounds);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 75e6229..e77daa6 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -770,8 +770,16 @@
public static final int KEYCODE_STEM_2 = 266;
/** Key code constant: Generic stem key 3 for Wear */
public static final int KEYCODE_STEM_3 = 267;
+ /** Key code constant: Directional Pad Up-Left */
+ public static final int KEYCODE_DPAD_UP_LEFT = 268;
+ /** Key code constant: Directional Pad Down-Left */
+ public static final int KEYCODE_DPAD_DOWN_LEFT = 269;
+ /** Key code constant: Directional Pad Up-Right */
+ public static final int KEYCODE_DPAD_UP_RIGHT = 270;
+ /** Key code constant: Directional Pad Down-Right */
+ public static final int KEYCODE_DPAD_DOWN_RIGHT = 271;
- private static final int LAST_KEYCODE = KEYCODE_STEM_3;
+ private static final int LAST_KEYCODE = KEYCODE_DPAD_DOWN_RIGHT;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 07984e9..4fc2ad3 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.transition.Scene;
import android.transition.Transition;
@@ -471,8 +472,24 @@
/**
* Called when a window is dismissed. This informs the callback that the
* window is gone, and it should finish itself.
+ * @param finishTask True if the task should also be finished.
*/
- public void onWindowDismissed();
+ void onWindowDismissed(boolean finishTask);
+ }
+
+ /** @hide */
+ public interface WindowStackCallback {
+ /** Called to move the window and its activity/task to a different stack container.
+ * For example, a window can move between
+ * {@link android.app.ActivityManager#FULLSCREEN_WORKSPACE_STACK_ID} stack and
+ * {@link android.app.ActivityManager#FREEFORM_WORKSPACE_STACK_ID} stack.
+ *
+ * @param stackId stack Id to change to.
+ */
+ void changeWindowStack(int stackId) throws RemoteException;
+
+ /** Returns the current stack Id for the window. */
+ int getWindowStackId() throws RemoteException;
}
public Window(Context context) {
@@ -659,7 +676,7 @@
/** @hide */
public final void dispatchOnWindowDismissed() {
if (mOnWindowDismissedCallback != null) {
- mOnWindowDismissedCallback.onWindowDismissed();
+ mOnWindowDismissedCallback.onWindowDismissed(false);
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 8364951..20fe61d 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -384,6 +384,10 @@
assigned.
*/
public Drawable getDrawable() {
+ if (mDrawable == mRecycleableBitmapDrawable) {
+ // Consider our cached version dirty since app code now has a reference to it
+ mRecycleableBitmapDrawable = null;
+ }
return mDrawable;
}
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 98bfd7d..50569d7 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -678,7 +678,7 @@
void startScroll(int start, int distance, int duration) {
mFinished = false;
- mStart = start;
+ mCurrentPosition = mStart = start;
mFinal = start + distance;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
@@ -712,7 +712,7 @@
boolean springback(int start, int min, int max) {
mFinished = true;
- mStart = mFinal = start;
+ mCurrentPosition = mStart = mFinal = start;
mVelocity = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
@@ -804,7 +804,7 @@
final float totalDuration = (float) Math.sqrt(
2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
- mStart = end;
+ mCurrentPosition = mStart = end;
mVelocity = (int) (- mDeceleration * totalDuration);
}
@@ -873,7 +873,7 @@
// Duration from start to null velocity
if (mDuration < mSplineDuration) {
// If the animation was clamped, we reached the edge
- mStart = mFinal;
+ mCurrentPosition = mStart = mFinal;
// TODO Better compute speed when edge was reached
mVelocity = (int) mCurrVelocity;
mDeceleration = getDeceleration(mVelocity);
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index b4cbf35..f9fa027 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -105,7 +105,10 @@
/** View that handles event dispatch and content transitions. */
private PopupDecorView mDecorView;
- /** The contents of the popup. */
+ /** View that holds the background and may animate during a transition. */
+ private View mBackgroundView;
+
+ /** The contents of the popup. May be identical to the background view. */
private View mContentView;
private boolean mFocusable;
@@ -1111,18 +1114,18 @@
if (aboveAnchor != mAboveAnchor) {
mAboveAnchor = aboveAnchor;
- if (mBackground != null) {
- // If the background drawable provided was a StateListDrawable with above-anchor
- // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
- // do the job.
+ if (mBackground != null && mBackgroundView != null) {
+ // If the background drawable provided was a StateListDrawable
+ // with above-anchor and below-anchor states, use those.
+ // Otherwise, rely on refreshDrawableState to do the job.
if (mAboveAnchorBackgroundDrawable != null) {
if (mAboveAnchor) {
- mDecorView.setBackground(mAboveAnchorBackgroundDrawable);
+ mBackgroundView.setBackground(mAboveAnchorBackgroundDrawable);
} else {
- mDecorView.setBackground(mBelowAnchorBackgroundDrawable);
+ mBackgroundView.setBackground(mBelowAnchorBackgroundDrawable);
}
} else {
- mDecorView.refreshDrawableState();
+ mBackgroundView.refreshDrawableState();
}
}
}
@@ -1164,22 +1167,21 @@
// When a background is available, we embed the content view within
// another view that owns the background drawable.
- final View backgroundView;
if (mBackground != null) {
- backgroundView = createBackgroundView(mContentView);
- backgroundView.setBackground(mBackground);
+ mBackgroundView = createBackgroundView(mContentView);
+ mBackgroundView.setBackground(mBackground);
} else {
- backgroundView = mContentView;
+ mBackgroundView = mContentView;
}
- mDecorView = createDecorView(backgroundView);
+ mDecorView = createDecorView(mBackgroundView);
// The background owner should be elevated so that it casts a shadow.
- backgroundView.setElevation(mElevation);
+ mBackgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
- final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
+ final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
@@ -1650,6 +1652,7 @@
// This needs to stay until after all transitions have ended since we
// need the reference to cancel transitions in preparePopup().
mDecorView = null;
+ mBackgroundView = null;
mIsTransitioningToDismiss = false;
}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index d954b71..55493c3 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -28,6 +28,7 @@
public class MetricsLogger implements MetricsConstants {
// Temporary constants go here, to await migration to MetricsConstants.
// next value is 239;
+ public static final int ACTION_ASSIST_LONG_PRESS = 239;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index da98a67..146c0f8 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -57,6 +57,11 @@
statsType);
app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
statsType);
+
+ if (DEBUG && app.wifiPowerMah != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + "ms tx=" +
+ txTime + "ms power=" + BatteryStatsHelper.makemAh(app.wifiPowerMah));
+ }
}
@Override
diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java
index c4e2ef6..3bd79f7e 100644
--- a/core/java/com/android/internal/os/WifiPowerEstimator.java
+++ b/core/java/com/android/internal/os/WifiPowerEstimator.java
@@ -16,11 +16,14 @@
package com.android.internal.os;
import android.os.BatteryStats;
+import android.util.Log;
/**
* Estimates WiFi power usage based on timers in BatteryStats.
*/
public class WifiPowerEstimator extends PowerCalculator {
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private static final String TAG = "WifiPowerEstimator";
private final double mWifiPowerPerPacket;
private final double mWifiPowerOn;
private final double mWifiPowerScan;
@@ -75,6 +78,10 @@
}
app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
+ if (DEBUG && app.wifiPowerMah != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": power=" +
+ BatteryStatsHelper.makemAh(app.wifiPowerMah));
+ }
}
@Override
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 2b39528..0bd8d85 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -72,6 +72,7 @@
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.DecorContentParent;
import com.android.internal.widget.FloatingToolbar;
+import com.android.internal.widget.NonClientDecorView;
import com.android.internal.widget.SwipeDismissLayout;
import android.app.ActivityManager;
@@ -1270,7 +1271,7 @@
* @param st The panel being initialized.
*/
protected boolean initializePanelDecor(PanelFeatureState st) {
- st.decorView = new DecorView(getContext(), st.featureId);
+ st.decorView = generateDecor(st.featureId);
st.gravity = Gravity.CENTER | Gravity.BOTTOM;
st.setStyle(getContext());
TypedArray a = getContext().obtainStyledAttributes(null,
@@ -2204,6 +2205,9 @@
private final Rect mFrameOffsets = new Rect();
+ // True if a non client area decor exists.
+ private boolean mHasNonClientDecor = false;
+
private boolean mChanging;
private Drawable mMenuBackground;
@@ -3148,53 +3152,59 @@
return;
}
- setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top
- + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right,
+ setPadding(mFramePadding.left + mBackgroundPadding.left,
+ mFramePadding.top + mBackgroundPadding.top,
+ mFramePadding.right + mBackgroundPadding.right,
mFramePadding.bottom + mBackgroundPadding.bottom);
requestLayout();
invalidate();
int opacity = PixelFormat.OPAQUE;
- // Note: if there is no background, we will assume opaque. The
- // common case seems to be that an application sets there to be
- // no background so it can draw everything itself. For that,
- // we would like to assume OPAQUE and let the app force it to
- // the slower TRANSLUCENT mode if that is really what it wants.
- Drawable bg = getBackground();
- Drawable fg = getForeground();
- if (bg != null) {
- if (fg == null) {
- opacity = bg.getOpacity();
- } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
- && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
- // If the frame padding is zero, then we can be opaque
- // if either the frame -or- the background is opaque.
- int fop = fg.getOpacity();
- int bop = bg.getOpacity();
- if (false)
- Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
- if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
- opacity = PixelFormat.OPAQUE;
- } else if (fop == PixelFormat.UNKNOWN) {
- opacity = bop;
- } else if (bop == PixelFormat.UNKNOWN) {
- opacity = fop;
+ if (windowHasShadow()) {
+ // If the window has a shadow, it must be translucent.
+ opacity = PixelFormat.TRANSLUCENT;
+ } else{
+ // Note: If there is no background, we will assume opaque. The
+ // common case seems to be that an application sets there to be
+ // no background so it can draw everything itself. For that,
+ // we would like to assume OPAQUE and let the app force it to
+ // the slower TRANSLUCENT mode if that is really what it wants.
+ Drawable bg = getBackground();
+ Drawable fg = getForeground();
+ if (bg != null) {
+ if (fg == null) {
+ opacity = bg.getOpacity();
+ } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+ && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+ // If the frame padding is zero, then we can be opaque
+ // if either the frame -or- the background is opaque.
+ int fop = fg.getOpacity();
+ int bop = bg.getOpacity();
+ if (false)
+ Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+ if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+ opacity = PixelFormat.OPAQUE;
+ } else if (fop == PixelFormat.UNKNOWN) {
+ opacity = bop;
+ } else if (bop == PixelFormat.UNKNOWN) {
+ opacity = fop;
+ } else {
+ opacity = Drawable.resolveOpacity(fop, bop);
+ }
} else {
- opacity = Drawable.resolveOpacity(fop, bop);
+ // For now we have to assume translucent if there is a
+ // frame with padding... there is no way to tell if the
+ // frame and background together will draw all pixels.
+ if (false)
+ Log.v(TAG, "Padding: " + mFramePadding);
+ opacity = PixelFormat.TRANSLUCENT;
}
- } else {
- // For now we have to assume translucent if there is a
- // frame with padding... there is no way to tell if the
- // frame and background together will draw all pixels.
- if (false)
- Log.v(TAG, "Padding: " + mFramePadding);
- opacity = PixelFormat.TRANSLUCENT;
}
+ if (false)
+ Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
}
if (false)
- Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
- if (false)
Log.v(TAG, "Selected default opacity: " + opacity);
mDefaultOpacity = opacity;
@@ -3401,8 +3411,7 @@
}
};
} else {
- ViewStub stub = (ViewStub) findViewById(
- R.id.action_mode_bar_stub);
+ ViewStub stub = (ViewStub) findViewById(R.id.action_mode_bar_stub);
if (stub != null) {
mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
}
@@ -3490,6 +3499,28 @@
.addOnPreDrawListener(mFloatingToolbarPreDrawListener);
}
+ // Set when the window is free floating and a non client decor frame was added.
+ void enableNonClientDecor(boolean enable) {
+ if (mHasNonClientDecor != enable) {
+ mHasNonClientDecor = enable;
+ if (getForeground() != null) {
+ drawableChanged();
+ }
+ }
+ }
+
+ // Returns true if the window has a non client decor.
+ private boolean windowHasNonClientDecor() {
+ return mHasNonClientDecor;
+ }
+
+ // Returns true if the Window is free floating and has a shadow. 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).
+ private boolean windowHasShadow() {
+ return windowHasNonClientDecor() && getElevation() > 0;
+ }
+
/**
* Clears out internal references when the action mode is destroyed.
*/
@@ -3599,8 +3630,8 @@
}
}
- protected DecorView generateDecor() {
- return new DecorView(getContext(), -1);
+ protected DecorView generateDecor(int featureId) {
+ return new DecorView(getContext(), featureId);
}
protected void setFeatureFromAttrs(int featureId, TypedArray attrs,
@@ -3879,8 +3910,15 @@
mDecor.startChanging();
+ NonClientDecorView nonClientDecorView = createNonClientDecorView();
View in = mLayoutInflater.inflate(layoutResource, null);
- decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ if (nonClientDecorView != null) {
+ decor.addView(nonClientDecorView,
+ new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ nonClientDecorView.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ } else {
+ decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
@@ -3936,6 +3974,54 @@
return contentParent;
}
+ // Free floating overlapping windows require a non client decor with a caption and shadow..
+ private NonClientDecorView createNonClientDecorView() {
+ boolean needsDecor = true;
+ NonClientDecorView nonClientDecorView = null;
+
+ final WindowManager.LayoutParams attrs = getAttributes();
+ // TODO(skuhne): Use the associated stack to figure out if the window is on the free style
+ // desktop, the side by side desktop or the full screen desktop. With that informations the
+ // choice is fairly easy to decide.
+ // => This is only a kludge for now to suppress fullscreen windows, recents, launcher, etc..
+ boolean isFullscreen =
+ 0 != ((mDecor.getWindowSystemUiVisibility() | mDecor.getSystemUiVisibility()) &
+ (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION));
+ boolean isApplication = attrs.type != TYPE_BASE_APPLICATION &&
+ attrs.type != TYPE_APPLICATION;
+
+ // We do not show the non client decor if...
+ // - this is a floating dialog (which is not a real window, e.g. it cannot be maximized).
+ // - it is not an application (special windows have special functions, e.g text selector).
+ // - the application is full screen, drawing everything (since the decor would be out of the
+ // screen in that case and could not be seen).
+ if (isFloating() || isFullscreen || isApplication) {
+ needsDecor = false;
+ }
+
+ if (needsDecor) {
+ // TODO(skuhne): If running in side by side mode on a device - turn off the shadow.
+ boolean windowHasShadow = true;
+ // Dependent on the brightness of the used title we either use the
+ // dark or the light button frame.
+ TypedValue value = new TypedValue();
+ getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
+ if (Color.brightness(value.data) < 0.5) {
+ nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate(
+ R.layout.non_client_decor_dark, null);
+ } else {
+ nonClientDecorView = (NonClientDecorView) mLayoutInflater.inflate(
+ R.layout.non_client_decor_light, null);
+ }
+ nonClientDecorView.setPhoneWindow(this, windowHasShadow);
+ }
+
+ // Tell the Decor if it has a non client decor.
+ mDecor.enableNonClientDecor(needsDecor);
+
+ return nonClientDecorView;
+ }
+
/** @hide */
public void alwaysReadCloseOnTouchAttr() {
mAlwaysReadCloseOnTouchAttr = true;
@@ -3943,7 +4029,7 @@
private void installDecor() {
if (mDecor == null) {
- mDecor = generateDecor();
+ mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
new file mode 100644
index 0000000..57039b7
--- /dev/null
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -0,0 +1,219 @@
+/*
+ * 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 android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.util.Log;
+import android.util.TypedValue;
+
+import android.view.ViewOutlineProvider;
+import android.view.WindowInsets;
+import com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+import java.util.List;
+
+/**
+ * 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(PhoneWindow owner, boolean windowHasShadow)} 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 ViewGroup implements View.OnClickListener {
+ 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;
+ boolean mWindowHasShadow = false;
+
+ // The current focus state of the window for updating the window elevation.
+ 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.
+ 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 windowHasShadow) {
+ mOwner = owner;
+ mWindowHasShadow = windowHasShadow;
+ if (mWindowHasShadow) {
+ // TODO(skuhne): Call setMaxElevation here as soon as b/22668382 got fixed.
+ updateElevation();
+ }
+ // 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 void onClick(View view) {
+ if (view.getId() == R.id.maximize_window) {
+ // TODO(skuhne): Add code to maximize window.
+ } else if (view.getId() == R.id.close_window) {
+ // TODO(skuhne): This is not the right way to kill an app and we should add a high level
+ // function for it.
+ final ActivityManager m =
+ (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
+ List<RunningTaskInfo> runningTaskInfoList = m.getRunningTasks(1);
+ if (!runningTaskInfoList.isEmpty()) {
+ try {
+ ActivityManagerNative.getDefault().removeTask(runningTaskInfoList.get(0).id);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't close task with the close button.");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ mWindowHasFocus = hasWindowFocus;
+ updateElevation();
+ super.onWindowFocusChanged(hasWindowFocus);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ measureChildren(widthMeasureSpec, heightMeasureSpec);
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int height = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // The system inset needs only to be applied to the caption. The client area of
+ // the window will automatically be adjusted by the the DecorView.
+ WindowInsets insets = getRootWindowInsets();
+ int systemMargin = insets.getSystemWindowInsetTop();
+
+ final int leftPos = getPaddingLeft();
+ final int rightPos = right - left - getPaddingRight();
+ final int topPos = getPaddingTop();
+ final int bottomPos = bottom - top - getPaddingBottom();
+
+ // On top we have the caption which has to fill left to right with a fixed height.
+ final int width = rightPos - leftPos;
+ final View caption = getChildAt(0);
+
+ // If the application changed its SystemUI metrics, we might also have to adapt
+ // our shadow elevation.
+ updateElevation();
+ mAllowUpdateElevation = true;
+
+ // Remove the decor temporarily if the window entered a full screen/immersive mode.
+ final int captionHeight = isFillingScreen() ? 0 : caption.getMeasuredHeight();
+ caption.layout(leftPos, topPos + systemMargin, leftPos + width,
+ topPos + systemMargin + captionHeight);
+
+ // Note: We should never have more then 1 additional item in here.
+ if (getChildCount() > 1) {
+ getChildAt(1).layout(leftPos, topPos + captionHeight, leftPos + width, bottomPos);
+ }
+ }
+
+ // Make sure that we never get more then one client area in our view.
+ @Override
+ public void addView(View child, int index, LayoutParams params) {
+ if (index >= 2 || getChildCount() >= 2) {
+ throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
+ }
+ super.addView(child, index, params);
+ }
+
+ // Returns true when the window is filling the entire screen and the non client area
+ // should not be shown.
+ 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)));
+ }
+
+ // 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".
+ private void updateElevation() {
+ float elevation = 0;
+ if (mWindowHasShadow) {
+ boolean fill = isFillingScreen();
+ elevation = fill ? 0 :
+ (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
+ DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
+ if (!mAllowUpdateElevation && !fill) {
+ // TODO(skuhne): Change this to setMaxElevation as soon as b/22668382 got fixed
+ // and remove this cludge.
+ 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);
+ }
+ }
+
+ private float dipToPx(float dip) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
+ getResources().getDisplayMetrics());
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 6b07a47..fc15b964 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -20,6 +20,10 @@
LOCAL_CFLAGS += -DENABLE_CPUSETS
endif
+ifneq ($(ENABLE_SCHED_BOOST),)
+ LOCAL_CFLAGS += -DENABLE_SCHED_BOOST
+endif
+
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index db88962..4c920dc 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -298,11 +298,11 @@
String8 storageSource;
if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
- storageSource = "/mnt/runtime_default";
+ storageSource = "/mnt/runtime/default";
} else if (mount_mode == MOUNT_EXTERNAL_READ) {
- storageSource = "/mnt/runtime_read";
+ storageSource = "/mnt/runtime/read";
} else if (mount_mode == MOUNT_EXTERNAL_WRITE) {
- storageSource = "/mnt/runtime_write";
+ storageSource = "/mnt/runtime/write";
} else {
// Sane default of no storage visible
return true;
@@ -408,6 +408,27 @@
}
}
+#ifdef ENABLE_SCHED_BOOST
+static void SetForkLoad(bool boost) {
+ // set scheduler knob to boost forked processes
+ pid_t currentPid = getpid();
+ // fits at most "/proc/XXXXXXX/sched_init_task_load\0"
+ char schedPath[35];
+ snprintf(schedPath, sizeof(schedPath), "/proc/%u/sched_init_task_load", currentPid);
+ int schedBoostFile = open(schedPath, O_WRONLY);
+ if (schedBoostFile < 0) {
+ ALOGW("Unable to set zygote scheduler boost");
+ return;
+ }
+ if (boost) {
+ write(schedBoostFile, "100\0", 4);
+ } else {
+ write(schedBoostFile, "0\0", 2);
+ }
+ close(schedBoostFile);
+}
+#endif
+
// Utility routine to fork zygote and specialize the child process.
static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
jint debug_flags, jobjectArray javaRlimits,
@@ -418,6 +439,10 @@
jstring instructionSet, jstring dataDir) {
SetSigChldHandler();
+#ifdef ENABLE_SCHED_BOOST
+ SetForkLoad(true);
+#endif
+
pid_t pid = fork();
if (pid == 0) {
@@ -558,6 +583,12 @@
}
} else if (pid > 0) {
// the parent process
+
+#ifdef ENABLE_SCHED_BOOST
+ // unset scheduler knob
+ SetForkLoad(false);
+#endif
+
}
return pid;
}
diff --git a/core/res/res/drawable/decor_close_button_dark.xml b/core/res/res/drawable/decor_close_button_dark.xml
new file mode 100644
index 0000000..e5ea1f3
--- /dev/null
+++ b/core/res/res/drawable/decor_close_button_dark.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="true"
+ android:drawable="@drawable/ic_decor_close_button_dark_focused" />
+ <item android:drawable="@drawable/ic_decor_close_button_dark_unfocused" />
+</selector>
+
diff --git a/core/res/res/drawable/decor_close_button_light.xml b/core/res/res/drawable/decor_close_button_light.xml
new file mode 100644
index 0000000..b77b2bd
--- /dev/null
+++ b/core/res/res/drawable/decor_close_button_light.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="true"
+ android:drawable="@drawable/ic_decor_close_button_light_focused" />
+ <item android:drawable="@drawable/ic_decor_close_button_light_unfocused" />
+</selector>
+
diff --git a/core/res/res/drawable/decor_maximize_button_dark.xml b/core/res/res/drawable/decor_maximize_button_dark.xml
new file mode 100644
index 0000000..5ea372b
--- /dev/null
+++ b/core/res/res/drawable/decor_maximize_button_dark.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="true"
+ android:drawable="@drawable/ic_decor_maximize_button_dark_focused" />
+ <item android:drawable="@drawable/ic_decor_maximize_button_dark_unfocused" />
+</selector>
+
diff --git a/core/res/res/drawable/decor_maximize_button_light.xml b/core/res/res/drawable/decor_maximize_button_light.xml
new file mode 100644
index 0000000..5bda131
--- /dev/null
+++ b/core/res/res/drawable/decor_maximize_button_light.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="true"
+ android:drawable="@drawable/ic_decor_maximize_button_light_focused" />
+ <item android:drawable="@drawable/ic_decor_maximize_button_light_unfocused" />
+</selector>
+
diff --git a/core/res/res/drawable/ic_decor_close_button_dark_focused.xml b/core/res/res/drawable/ic_decor_close_button_dark_focused.xml
new file mode 100644
index 0000000..d7b167dd
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_close_button_dark_focused.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml b/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml
new file mode 100644
index 0000000..e2e81b9
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#33FFFFFF"
+ android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_close_button_light_focused.xml b/core/res/res/drawable/ic_decor_close_button_light_focused.xml
new file mode 100644
index 0000000..0794ed3
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_close_button_light_focused.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#ff000000"
+ android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml b/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml
new file mode 100644
index 0000000..bd1db51
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml
@@ -0,0 +1,29 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#33000000"
+ android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml b/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml
new file mode 100644
index 0000000..73d808b
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml
@@ -0,0 +1,32 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
+ <path
+ android:fillColor="#B2FFFFFF"
+ android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml b/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml
new file mode 100644
index 0000000..dc79e10
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml
@@ -0,0 +1,32 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#33FFFFFF"
+ android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
+ <path
+ android:fillColor="#33FFFFFF"
+ android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml b/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml
new file mode 100644
index 0000000..c23390e
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml
@@ -0,0 +1,32 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
+ <path
+ android:fillColor="#B2000000"
+ android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml b/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml
new file mode 100644
index 0000000..a194a39
--- /dev/null
+++ b/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml
@@ -0,0 +1,32 @@
+<!--
+Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="#33000000"
+ android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
+ <path
+ android:fillColor="#33000000"
+ android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+ </group>
+</vector>
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/core/res/res/drawable/non_client_decor_title.xml
new file mode 100644
index 0000000..e50daea
--- /dev/null
+++ b/core/res/res/drawable/non_client_decor_title.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<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" />
+</selector>
diff --git a/core/res/res/drawable/non_client_decor_title_focused.xml b/core/res/res/drawable/non_client_decor_title_focused.xml
new file mode 100644
index 0000000..7d1c230
--- /dev/null
+++ b/core/res/res/drawable/non_client_decor_title_focused.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape android:shape="rectangle"
+ android:tintMode="multiply"
+ android:tint="#D8D8D8"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Fading the primary color to 85% blackness -->
+ <solid android:color="?android:attr/colorPrimary" />
+</shape>
diff --git a/core/res/res/drawable/non_client_decor_title_unfocused.xml b/core/res/res/drawable/non_client_decor_title_unfocused.xml
new file mode 100644
index 0000000..2846d8ca
--- /dev/null
+++ b/core/res/res/drawable/non_client_decor_title_unfocused.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape android:shape="rectangle"
+ android:tintMode="multiply"
+ android:tint="#F2F2F2"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Fading the primary color to 95% blackness -->
+ <solid android:color="?android:attr/colorPrimary"/>
+</shape>
diff --git a/core/res/res/layout/non_client_decor_dark.xml b/core/res/res/layout/non_client_decor_dark.xml
new file mode 100644
index 0000000..00b4255
--- /dev/null
+++ b/core/res/res/layout/non_client_decor_dark.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|start"
+ android:descendantFocusability="beforeDescendants" >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_gravity="end"
+ android:layout_height="wrap_content"
+ android:background="@drawable/non_client_decor_title" >
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+ <Button
+ android:id="@+id/maximize_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/maximize_button_text"
+ android:background="@drawable/decor_maximize_button_dark" />
+ <Button
+ android:id="@+id/close_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/close_button_text"
+ android:background="@drawable/decor_close_button_dark" />
+ </LinearLayout>
+</com.android.internal.widget.NonClientDecorView>
diff --git a/core/res/res/layout/non_client_decor_light.xml b/core/res/res/layout/non_client_decor_light.xml
new file mode 100644
index 0000000..0ce8fa7
--- /dev/null
+++ b/core/res/res/layout/non_client_decor_light.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|start"
+ android:descendantFocusability="beforeDescendants" >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_gravity="end"
+ android:layout_height="wrap_content"
+ android:background="@drawable/non_client_decor_title" >
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+ <Button
+ android:id="@+id/maximize_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/maximize_button_text"
+ android:background="@drawable/decor_maximize_button_light" />
+ <Button
+ android:id="@+id/close_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/close_button_text"
+ android:background="@drawable/decor_close_button_light" />
+ </LinearLayout>
+</com.android.internal.widget.NonClientDecorView>
diff --git a/core/res/res/values-mcc202-mnc05/config.xml b/core/res/res/values-mcc202-mnc05/config.xml
deleted file mode 100644
index c74f2d7..0000000
--- a/core/res/res/values-mcc202-mnc05/config.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You my 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
- <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
- <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
- <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
- <integer-array translatable="false" name="config_tether_upstream_types">
- <item>1</item>
- <item>4</item>
- <item>7</item>
- <item>9</item>
- </integer-array>
-
- <!-- String containing the apn value for tethering. May be overriden by secure settings
- TETHER_DUN_APN. Value is a comma separated series of strings:
- "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
- Or string format of ApnSettingV3.
- note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
- <string-array translatable="false" name="config_tether_apndata">
- <item>Vf Tethering,internet.vodafone.gr,,,,,,,,,202,05,,DUN</item>
- </string-array>
-
-</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 712272f..99beef8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1810,6 +1810,10 @@
<enum name="KEYCODE_STEM_1" value="265" />
<enum name="KEYCODE_STEM_2" value="266" />
<enum name="KEYCODE_STEM_3" value="267" />
+ <enum name="KEYCODE_DPAD_UP_LEFT" value="268" />
+ <enum name="KEYCODE_DPAD_DOWN_LEFT" value="269" />
+ <enum name="KEYCODE_DPAD_UP_RIGHT" value="270" />
+ <enum name="KEYCODE_DPAD_DOWN_RIGHT" value="271" />
</attr>
<!-- ***************************************************************** -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index aa15b5a..398c584 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -505,16 +505,16 @@
<bool translatable="false" name="config_wifi_ssid_white_list_enable">true</bool>
<!-- Idle Receive current for wifi radio. 0 by default-->
- <integer translatable="false" name="config_wifi_idle_receive_cur_ma">1</integer>
+ <integer translatable="false" name="config_wifi_idle_receive_cur_ma">0</integer>
<!-- Rx current for wifi radio. 0 by default-->
- <integer translatable="false" name="config_wifi_active_rx_cur_ma">2</integer>
+ <integer translatable="false" name="config_wifi_active_rx_cur_ma">0</integer>
<!-- Tx current for wifi radio. 0 by default-->
- <integer translatable="false" name="config_wifi_tx_cur_ma">3</integer>
+ <integer translatable="false" name="config_wifi_tx_cur_ma">0</integer>
<!-- Operating volatage for wifi radio. 0 by default-->
- <integer translatable="false" name="config_wifi_operating_voltage_mv">4</integer>
+ <integer translatable="false" name="config_wifi_operating_voltage_mv">0</integer>
<!-- Flag indicating whether the we should enable the automatic brightness in Settings.
Software implementation will be used if config_hardware_auto_brightness_available is not set -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7cdad4b..ac6204d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4056,6 +4056,12 @@
<!-- Content description for the button that closes the floating toolbar overflow. [CHAR LIMIT=NONE] -->
<string name="floating_toolbar_close_overflow_description">Close overflow</string>
+ <!-- Free style window strings -->
+ <!-- Accessibility text for the maximize window button -->
+ <string name="maximize_button_text">Maximize</string>
+ <!-- Accessibility text for the close window button -->
+ <string name="close_button_text">Close</string>
+
<!-- Ellipsis character to appear in notification templates, e.g.
notification_template_material_inbox.xml.
DO NOT TRANSLATE -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5303d5f..a45d907 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1939,6 +1939,10 @@
<!-- From Phone -->
<java-symbol type="bool" name="config_built_in_sip_phone" />
+ <java-symbol type="id" name="maximize_window" />
+ <java-symbol type="id" name="close_window" />
+ <java-symbol type="layout" name="non_client_decor_light" />
+ <java-symbol type="layout" name="non_client_decor_dark" />
<!-- From TelephonyProvider -->
<java-symbol type="xml" name="apns" />
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index bdb1f58..fef65ee 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.util.SparseIntArray;
+import java.util.TreeSet;
+
/**
* Class to provide information about the audio devices.
*/
@@ -109,6 +111,14 @@
* A device type connected over IP.
*/
public static final int TYPE_IP = 20;
+ /**
+ * @hide
+ * A remote-submix device.
+ * We need this for CTS, but it is not part of the external API.
+ * FIXME It has been suggested that CTS should only be testing public APIs.
+ * Consider this for a public API.
+ */
+ public static final int TYPE_REMOTE_SUBMIX = 0x7FFF;
private final AudioDevicePort mPort;
@@ -156,6 +166,8 @@
/**
* @return An array of sample rates supported by the audio device.
+ *
+ * Note: an empty array indicates that the device supports arbitrary rates.
*/
public @NonNull int[] getSampleRates() {
return mPort.samplingRates();
@@ -166,6 +178,8 @@
* {@link AudioFormat#CHANNEL_OUT_7POINT1}) for which this audio device can be configured.
*
* @see AudioFormat
+ *
+ * Note: an empty array indicates that the device supports arbitrary channel masks.
*/
public @NonNull int[] getChannelMasks() {
return mPort.channelMasks();
@@ -175,6 +189,8 @@
* @return An array of channel index masks for which this audio device can be configured.
*
* @see AudioFormat
+ *
+ * Note: an empty array indicates that the device supports arbitrary channel index masks.
*/
public @NonNull int[] getChannelIndexMasks() {
return mPort.channelIndexMasks();
@@ -183,15 +199,28 @@
/**
* @return An array of channel counts (1, 2, 4, ...) for which this audio device
* can be configured.
+ *
+ * Note: an empty array indicates that the device supports arbitrary channel counts.
*/
public @NonNull int[] getChannelCounts() {
- int[] masks = getChannelMasks();
- int[] counts = new int[masks.length];
- // TODO: consider channel index masks
- for (int mask_index = 0; mask_index < masks.length; mask_index++) {
- counts[mask_index] = isSink()
- ? AudioFormat.channelCountFromOutChannelMask(masks[mask_index])
- : AudioFormat.channelCountFromInChannelMask(masks[mask_index]);
+ TreeSet<Integer> countSet = new TreeSet<Integer>();
+
+ // Channel Masks
+ for (int mask : getChannelMasks()) {
+ countSet.add(isSink() ?
+ AudioFormat.channelCountFromOutChannelMask(mask)
+ : AudioFormat.channelCountFromInChannelMask(mask));
+ }
+
+ // Index Masks
+ for (int index_mask : getChannelIndexMasks()) {
+ countSet.add(Integer.bitCount(index_mask));
+ }
+
+ int[] counts = new int[countSet.size()];
+ int index = 0;
+ for (int count : countSet) {
+ counts[index++] = count;
}
return counts;
}
@@ -205,6 +234,8 @@
* integer precision to that device.
*
* @see AudioFormat
+ *
+ * Note: an empty array indicates that the device supports arbitrary encodings.
*/
public @NonNull int[] getEncodings() {
return AudioFormat.filterPublicFormats(mPort.formats());
@@ -255,6 +286,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_FM, TYPE_FM);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_AUX_LINE, TYPE_AUX_LINE);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -272,10 +304,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_SPDIF, TYPE_LINE_DIGITAL);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
-
- // not covered here, legacy
- //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
- //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
// privileges mapping to output device
EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java
index b37e02c..5522d36 100644
--- a/media/java/android/media/MediaSync.java
+++ b/media/java/android/media/MediaSync.java
@@ -55,7 +55,7 @@
* }
* }, null);
* // This needs to be done since sync is paused on creation.
- * sync.setPlaybackRate(1.0f, MediaSync.PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
+ * sync.setPlaybackParams(new PlaybackParams().setSpeed(1.f));
*
* for (;;) {
* ...
@@ -69,7 +69,7 @@
* ...
* ...
* }
- * sync.setPlaybackRate(0.0f, MediaSync.PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
+ * sync.setPlaybackParams(new PlaybackParams().setSpeed(0.f));
* sync.release();
* sync = null;
*
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 89230fe..7197dc0 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -24,7 +24,7 @@
import android.os.RemoteException;
import android.util.Log;
-import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
/**
* This class is the public application interface to the MIDI service.
@@ -61,8 +61,8 @@
private final IMidiManager mService;
private final IBinder mToken = new Binder();
- private HashMap<DeviceCallback,DeviceListener> mDeviceListeners =
- new HashMap<DeviceCallback,DeviceListener>();
+ private ConcurrentHashMap<DeviceCallback,DeviceListener> mDeviceListeners =
+ new ConcurrentHashMap<DeviceCallback,DeviceListener>();
// Binder stub for receiving device notifications from MidiService
private class DeviceListener extends IMidiDeviceListener.Stub {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BandSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/BandSelectManager.java
index f2bde0e..74170f5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BandSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BandSelectManager.java
@@ -18,7 +18,6 @@
import static com.android.documentsui.Events.isMouseEvent;
import static com.android.internal.util.Preconditions.checkState;
-import static java.lang.String.format;
import android.graphics.Point;
import android.graphics.Rect;
@@ -38,6 +37,7 @@
public class BandSelectManager extends RecyclerView.SimpleOnItemTouchListener {
private static final int NOT_SELECTED = -1;
+ private static final int NOT_SET = -1;
// For debugging purposes.
private static final String TAG = "BandSelectManager";
@@ -50,14 +50,137 @@
private boolean mIsBandSelectActive = false;
private Point mOrigin;
+ private Point mPointer;
private Rect mBounds;
- // Maintain the last selection made by band, so if bounds shink back, we can unselect
- // the respective items.
- // Track information
+ // Maintain the last selection made by band, so if bounds shrink back, we can deselect
+ // the respective items.
private int mCursorDeltaY = 0;
private int mFirstSelected = NOT_SELECTED;
+ // The time at which the current band selection-induced scroll began. If no scroll is in
+ // progress, the value is NOT_SET.
+ private long mScrollStartTime = NOT_SET;
+ private final Runnable mScrollRunnable = new Runnable() {
+ /**
+ * The number of milliseconds of scrolling at which scroll speed continues to increase. At
+ * first, the scroll starts slowly; then, the rate of scrolling increases until it reaches
+ * its maximum value at after this many milliseconds.
+ */
+ private static final long SCROLL_ACCELERATION_LIMIT_TIME_MS = 2000;
+
+ @Override
+ public void run() {
+ // Compute the number of pixels the pointer's y-coordinate is past the view. Negative
+ // values mean the pointer is at or before the top of the view, and positive values mean
+ // that the pointer is at or after the bottom of the view. Note that one additional
+ // pixel is added here so that the view still scrolls when the pointer is exactly at the
+ // top or bottom.
+ int pixelsPastView = 0;
+ if (mPointer.y <= 0) {
+ pixelsPastView = mPointer.y - 1;
+ } else if (mPointer.y >= mRecyclerView.getHeight() - 1) {
+ pixelsPastView = mPointer.y - mRecyclerView.getHeight() + 1;
+ }
+
+ if (!mIsBandSelectActive || pixelsPastView == 0) {
+ // If band selection is inactive, or if it is active but not at the edge of the
+ // view, no scrolling is necessary.
+ mScrollStartTime = NOT_SET;
+ return;
+ }
+
+ if (mScrollStartTime == NOT_SET) {
+ // If the pointer was previously not at the edge of the view but now is, set the
+ // start time for the scroll.
+ mScrollStartTime = System.currentTimeMillis();
+ }
+
+ // Compute the number of pixels to scroll, and scroll that many pixels.
+ final int numPixels = computeNumPixelsToScroll(
+ pixelsPastView, System.currentTimeMillis() - mScrollStartTime);
+ mRecyclerView.scrollBy(0, numPixels);
+
+ // Adjust the y-coordinate of the origin the opposite number of pixels so that the
+ // origin remains in the same place relative to the view's items.
+ mOrigin.y -= numPixels;
+ resizeBandSelectRectangle();
+
+ mRecyclerView.removeCallbacks(mScrollRunnable);
+ mRecyclerView.postOnAnimation(this);
+ }
+
+ /**
+ * Computes the number of pixels to scroll based on how far the pointer is past the end of
+ * the view and how long it has been there. Roughly based on ItemTouchHelper's algorithm for
+ * computing the number of pixels to scroll when an item is dragged to the end of a
+ * {@link RecyclerView}.
+ * @param pixelsPastView
+ * @param scrollDuration
+ * @return
+ */
+ private int computeNumPixelsToScroll(int pixelsPastView, long scrollDuration) {
+ final int maxScrollStep = computeMaxScrollStep(mRecyclerView);
+ final int direction = (int) Math.signum(pixelsPastView);
+ final int absPastView = Math.abs(pixelsPastView);
+
+ // Calculate the ratio of how far out of the view the pointer currently resides to the
+ // entire height of the view.
+ final float outOfBoundsRatio = Math.min(
+ 1.0f, (float) absPastView / mRecyclerView.getHeight());
+ // Interpolate this ratio and use it to compute the maximum scroll that should be
+ // possible for this step.
+ final float cappedScrollStep =
+ direction * maxScrollStep * smoothOutOfBoundsRatio(outOfBoundsRatio);
+
+ // Likewise, calculate the ratio of the time spent in the scroll to the limit.
+ final float timeRatio = Math.min(
+ 1.0f, (float) scrollDuration / SCROLL_ACCELERATION_LIMIT_TIME_MS);
+ // Interpolate this ratio and use it to compute the final number of pixels to scroll.
+ final int numPixels = (int) (cappedScrollStep * smoothTimeRatio(timeRatio));
+
+ // If the final number of pixels to scroll ends up being 0, the view should still scroll
+ // at least one pixel.
+ return numPixels != 0 ? numPixels : direction;
+ }
+
+ /**
+ * Computes the maximum scroll allowed for a given animation frame. Currently, this
+ * defaults to the height of the view, but this could be tweaked if this results in scrolls
+ * that are too fast or too slow.
+ * @param rv
+ * @return
+ */
+ private int computeMaxScrollStep(RecyclerView rv) {
+ return rv.getHeight();
+ }
+
+ /**
+ * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends at
+ * (1,1) and quickly approaches 1 near the start of that interval. This ensures that drags
+ * that are at the edge or barely past the edge of the view still cause sufficient
+ * scrolling. The equation y=(x-1)^5+1 is used, but this could also be tweaked if needed.
+ * @param ratio A ratio which is in the range [0, 1].
+ * @return A "smoothed" value, also in the range [0, 1].
+ */
+ private float smoothOutOfBoundsRatio(float ratio) {
+ return (float) Math.pow(ratio - 1.0f, 5) + 1.0f;
+ }
+
+ /**
+ * Interpolates the given time ratio on a curve which starts at (0,0) and ends at (1,1) and
+ * stays close to 0 for most input values except those very close to 1. This ensures that
+ * scrolls start out very slowly but speed up drastically after the scroll has been in
+ * progress close to SCROLL_ACCELERATION_LIMIT_TIME_MS. The equation y=x^5 is used, but this
+ * could also be tweaked if needed.
+ * @param ratio A ratio which is in the range [0, 1].
+ * @return A "smoothed" value, also in the range [0, 1].
+ */
+ private float smoothTimeRatio(float ratio) {
+ return (float) Math.pow(ratio, 5);
+ }
+ };
+
/**
* @param recyclerView
* @param multiSelectManager
@@ -95,41 +218,48 @@
return;
}
- Point point = new Point((int) e.getX(), (int) e.getY());
+ mPointer = new Point((int) e.getX(), (int) e.getY());
if (!mIsBandSelectActive) {
- startBandSelect(point);
+ startBandSelect();
}
- resizeBandSelectRectangle(point);
+ scrollViewIfNecessary();
+ resizeBandSelectRectangle();
selectChildrenCoveredBySelection();
}
/**
* Starts band select by adding the drawable to the RecyclerView's overlay.
- * @param origin The starting point of the selection.
*/
- private void startBandSelect(Point origin) {
- if (DEBUG) Log.d(TAG, "Starting band select from (" + origin.x + "," + origin.y + ").");
+ private void startBandSelect() {
+ if (DEBUG) Log.d(TAG, "Starting band select from (" + mPointer.x + "," + mPointer.y + ").");
mIsBandSelectActive = true;
- mOrigin = origin;
+ mOrigin = mPointer;
mRecyclerView.getOverlay().add(mRegionSelectorDrawable);
}
/**
+ * Scrolls the view if necessary.
+ */
+ private void scrollViewIfNecessary() {
+ mRecyclerView.removeCallbacks(mScrollRunnable);
+ mScrollRunnable.run();
+ mRecyclerView.invalidate();
+ }
+
+ /**
* Resizes the band select rectangle by using the origin and the current pointer positoin as
* two opposite corners of the selection.
- * @param pointerPosition
*/
- private void resizeBandSelectRectangle(Point pointerPosition) {
-
+ private void resizeBandSelectRectangle() {
if (mBounds != null) {
- mCursorDeltaY = pointerPosition.y - mBounds.bottom;
+ mCursorDeltaY = mPointer.y - mBounds.bottom;
}
- mBounds = new Rect(Math.min(mOrigin.x, pointerPosition.x),
- Math.min(mOrigin.y, pointerPosition.y),
- Math.max(mOrigin.x, pointerPosition.x),
- Math.max(mOrigin.y, pointerPosition.y));
+ mBounds = new Rect(Math.min(mOrigin.x, mPointer.x),
+ Math.min(mOrigin.y, mPointer.y),
+ Math.max(mOrigin.x, mPointer.x),
+ Math.max(mOrigin.y, mPointer.y));
mRegionSelectorDrawable.setBounds(mBounds);
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5183c35..55d85206 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -557,6 +557,7 @@
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (DEBUG) Log.d(TAG, "received broadcast " + action);
@@ -610,6 +611,7 @@
private final BroadcastReceiver mBroadcastAllReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(action)) {
@@ -724,6 +726,7 @@
return new SimData(state, slotId, subId);
}
+ @Override
public String toString() {
return "SimData{state=" + simState + ",slotId=" + slotId + ",subId=" + subId + "}";
}
@@ -939,7 +942,9 @@
}
private boolean shouldListenForFingerprint() {
- return mKeyguardIsVisible && !mSwitchingUser;
+ return mKeyguardIsVisible && !mSwitchingUser &&
+ mTrustManager.hasUserAuthenticatedSinceBoot(
+ ActivityManager.getCurrentUser());
}
private void startListeningForFingerprint() {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 9a63642..c9db29e 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -17,8 +17,10 @@
package com.android.mtp;
import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.graphics.Point;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
@@ -170,7 +172,37 @@
try {
final MtpDocument document =
mMtpManager.getDocument(identifier.mDeviceId, identifier.mObjectHandle);
- return mPipeManager.readDocument(mMtpManager, identifier, document.getSize());
+ return mPipeManager.readDocument(mMtpManager, identifier);
+ } catch (IOException error) {
+ throw new FileNotFoundException(error.getMessage());
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openDocumentThumbnail(
+ String documentId,
+ Point sizeHint,
+ CancellationSignal signal) throws FileNotFoundException {
+ final Identifier identifier = Identifier.createFromDocumentId(documentId);
+ try {
+ return new AssetFileDescriptor(
+ mPipeManager.readThumbnail(mMtpManager, identifier),
+ 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ } catch (IOException error) {
+ throw new FileNotFoundException(error.getMessage());
+ }
+ }
+
+ @Override
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ try {
+ final Identifier identifier = Identifier.createFromDocumentId(documentId);
+ final int parentHandle =
+ mMtpManager.getParent(identifier.mDeviceId, identifier.mObjectHandle);
+ mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
+ notifyChildDocumentsChange(new Identifier(
+ identifier.mDeviceId, identifier.mStorageId, parentHandle).toDocumentId());
} catch (IOException error) {
throw new FileNotFoundException(error.getMessage());
}
@@ -201,20 +233,6 @@
}
}
- @Override
- public void deleteDocument(String documentId) throws FileNotFoundException {
- try {
- final Identifier identifier = Identifier.createFromDocumentId(documentId);
- final int parentHandle =
- mMtpManager.getParent(identifier.mDeviceId, identifier.mObjectHandle);
- mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
- notifyChildDocumentsChange(new Identifier(
- identifier.mDeviceId, identifier.mStorageId, parentHandle).toDocumentId());
- } catch (IOException error) {
- throw new FileNotFoundException(error.getMessage());
- }
- }
-
boolean hasOpenedDevices() {
return mMtpManager.getOpenedDeviceIds().length != 0;
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 5057451..d4ee668 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -21,6 +21,7 @@
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.mtp.MtpDevice;
+import android.os.ParcelFileDescriptor;
import android.util.SparseArray;
import java.io.FileNotFoundException;
@@ -110,6 +111,11 @@
return device.getObject(objectHandle, expectedSize);
}
+ synchronized byte[] getThumbnail(int deviceId, int objectHandle) throws IOException {
+ final MtpDevice device = getDevice(deviceId);
+ return device.getThumbnail(objectHandle);
+ }
+
synchronized void deleteDocument(int deviceId, int objectHandle) throws IOException {
final MtpDevice device = getDevice(deviceId);
if (!device.deleteObject(objectHandle)) {
@@ -126,6 +132,11 @@
return result;
}
+ synchronized void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target)
+ throws IOException {
+ throw new UnsupportedOperationException("Importing files is not supported.");
+ }
+
private MtpDevice getDevice(int deviceId) throws IOException {
final MtpDevice device = mDevices.get(deviceId);
if (device == null) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
index a2c7772..7f498f5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
@@ -34,52 +34,78 @@
this.mExecutor = executor;
}
- ParcelFileDescriptor readDocument(
- final MtpManager model,
- final Identifier identifier,
- final int expectedSize) throws IOException {
- final Task task = new Task() {
- @Override
- byte[] getBytes() throws IOException {
- // TODO: Use importFile to ParcelFileDescripter after implementing this.
- return model.getObject(
- identifier.mDeviceId, identifier.mObjectHandle, expectedSize);
- }
- };
+ ParcelFileDescriptor readDocument(MtpManager model, Identifier identifier) throws IOException {
+ final Task task = new ImportFileTask(model, identifier);
+ mExecutor.execute(task);
+ return task.getReadingFileDescriptor();
+ }
+
+ ParcelFileDescriptor readThumbnail(MtpManager model, Identifier identifier) throws IOException {
+ final Task task = new GetThumbnailTask(model, identifier);
mExecutor.execute(task);
return task.getReadingFileDescriptor();
}
private static abstract class Task implements Runnable {
- private final ParcelFileDescriptor[] mDescriptors;
+ protected final MtpManager mModel;
+ protected final Identifier mIdentifier;
+ protected final ParcelFileDescriptor[] mDescriptors;
- Task() throws IOException {
+ Task(MtpManager model, Identifier identifier) throws IOException {
+ mModel = model;
+ mIdentifier = identifier;
mDescriptors = ParcelFileDescriptor.createReliablePipe();
}
- abstract byte[] getBytes() throws IOException;
-
- @Override
- public void run() {
- try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
- new ParcelFileDescriptor.AutoCloseOutputStream(mDescriptors[1])) {
- try {
- final byte[] bytes = getBytes();
- stream.write(bytes);
- } catch (IOException error) {
- mDescriptors[1].closeWithError("Failed to load bytes.");
- return;
- }
- } catch (IOException closeError) {
- Log.d(MtpDocumentsProvider.TAG, closeError.getMessage());
- }
- }
-
ParcelFileDescriptor getReadingFileDescriptor() {
return mDescriptors[0];
}
}
+ private static class ImportFileTask extends Task {
+ ImportFileTask(MtpManager model, Identifier identifier) throws IOException {
+ super(model, identifier);
+ }
+
+ @Override
+ public void run() {
+ try {
+ mModel.importFile(
+ mIdentifier.mDeviceId, mIdentifier.mObjectHandle, mDescriptors[1]);
+ mDescriptors[1].close();
+ } catch (IOException error) {
+ try {
+ mDescriptors[1].closeWithError("Failed to stream a file.");
+ } catch (IOException closeError) {
+ Log.w(MtpDocumentsProvider.TAG, closeError.getMessage());
+ }
+ }
+ }
+ }
+
+ private static class GetThumbnailTask extends Task {
+ GetThumbnailTask(MtpManager model, Identifier identifier) throws IOException {
+ super(model, identifier);
+ }
+
+ @Override
+ public void run() {
+ try {
+ try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(mDescriptors[1])) {
+ try {
+ stream.write(mModel.getThumbnail(
+ mIdentifier.mDeviceId, mIdentifier.mObjectHandle));
+ } catch (IOException error) {
+ mDescriptors[1].closeWithError("Failed to stream a thumbnail.");
+ }
+ }
+ } catch (IOException closeError) {
+ Log.w(MtpDocumentsProvider.TAG, closeError.getMessage());
+ }
+ }
+ }
+
void close() {
mExecutor.shutdown();
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
index 3c295ee..35918e1 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java
@@ -27,30 +27,60 @@
@SmallTest
public class PipeManagerTest extends AndroidTestCase {
+ private static final byte[] HELLO_BYTES = new byte[] { 'h', 'e', 'l', 'l', 'o' };
+
+ private TestMtpManager mtpManager;
+ private ExecutorService executor;
+ private PipeManager pipeManager;
+
+ @Override
+ public void setUp() {
+ mtpManager = new TestMtpManager(getContext());
+ executor = Executors.newSingleThreadExecutor();
+ pipeManager = new PipeManager(executor);
+ }
+
public void testReadDocument_basic() throws Exception {
- final TestMtpManager mtpManager = new TestMtpManager(getContext());
- final byte[] expectedBytes = new byte[] { 'h', 'e', 'l', 'l', 'o' };
- mtpManager.setObjectBytes(0, 1, 5, expectedBytes);
- final ExecutorService executor = Executors.newSingleThreadExecutor();
- final PipeManager pipeManager = new PipeManager(executor);
+ mtpManager.setImportFileBytes(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = pipeManager.readDocument(
- mtpManager, new Identifier(0, 0, 1), 5);
+ mtpManager, new Identifier(0, 0, 1));
+ assertDescriptor(descriptor, HELLO_BYTES);
+ }
+
+ public void testReadDocument_error() throws Exception {
+ final ParcelFileDescriptor descriptor =
+ pipeManager.readDocument(mtpManager, new Identifier(0, 0, 1));
+ assertDescriptorError(descriptor);
+ }
+
+ public void testReadThumbnail_basic() throws Exception {
+ mtpManager.setThumbnail(0, 1, HELLO_BYTES);
+ final ParcelFileDescriptor descriptor = pipeManager.readThumbnail(
+ mtpManager, new Identifier(0, 0, 1));
+ assertDescriptor(descriptor, HELLO_BYTES);
+ }
+
+ public void testReadThumbnail_error() throws Exception {
+ final ParcelFileDescriptor descriptor =
+ pipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1));
+ assertDescriptorError(descriptor);
+ }
+
+ private void assertDescriptor(ParcelFileDescriptor descriptor, byte[] expectedBytes)
+ throws IOException, InterruptedException {
+ executor.awaitTermination(1000, TimeUnit.MILLISECONDS);
try (final ParcelFileDescriptor.AutoCloseInputStream stream =
new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) {
- final byte[] results = new byte[100];
- assertEquals(5, stream.read(results));
- for (int i = 0; i < 5; i++) {
+ byte[] results = new byte[100];
+ assertEquals(expectedBytes.length, stream.read(results));
+ for (int i = 0; i < expectedBytes.length; i++) {
assertEquals(expectedBytes[i], results[i]);
}
}
}
- public void testReadDocument_error() throws Exception {
- final TestMtpManager mtpManager = new TestMtpManager(getContext());
- final ExecutorService executor = Executors.newSingleThreadExecutor();
- final PipeManager pipeManager = new PipeManager(executor);
- final ParcelFileDescriptor descriptor =
- pipeManager.readDocument(mtpManager, new Identifier(0, 0, 1), 5);
+ private void assertDescriptorError(ParcelFileDescriptor descriptor)
+ throws InterruptedException {
executor.awaitTermination(1000, TimeUnit.MILLISECONDS);
try {
descriptor.checkError();
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 48bc3b5..725b765 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -17,6 +17,7 @@
package com.android.mtp;
import android.content.Context;
+import android.os.ParcelFileDescriptor;
import java.io.IOException;
import java.util.Arrays;
@@ -35,8 +36,9 @@
private final Set<Integer> mOpenedDevices = new TreeSet<Integer>();
private final Map<Integer, MtpRoot[]> mRoots = new HashMap<Integer, MtpRoot[]>();
private final Map<String, MtpDocument> mDocuments = new HashMap<String, MtpDocument>();
- private final Map<String, byte[]> mObjectBytes = new HashMap<String, byte[]>();
+ private final Map<String, byte[]> mThumbnailBytes = new HashMap<String, byte[]>();
private final Map<String, Integer> mParents = new HashMap<String, Integer>();
+ private final Map<String, byte[]> mImportFileBytes = new HashMap<String, byte[]>();
TestMtpManager(Context context) {
super(context);
@@ -54,8 +56,12 @@
mDocuments.put(pack(deviceId, objectHandle), document);
}
- void setObjectBytes(int deviceId, int objectHandle, int expectedSize, byte[] bytes) {
- mObjectBytes.put(pack(deviceId, objectHandle, expectedSize), bytes);
+ void setImportFileBytes(int deviceId, int objectHandle, byte[] bytes) {
+ mImportFileBytes.put(pack(deviceId, objectHandle), bytes);
+ }
+
+ void setThumbnail(int deviceId, int objectHandle, byte[] bytes) {
+ mThumbnailBytes.put(pack(deviceId, objectHandle), bytes);
}
void setParent(int deviceId, int objectHandle, int parentObjectHandle) {
@@ -93,12 +99,25 @@
}
@Override
- byte[] getObject(int deviceId, int storageId, int expectedSize) throws IOException {
- final String key = pack(deviceId, storageId, expectedSize);
- if (mObjectBytes.containsKey(key)) {
- return mObjectBytes.get(key);
+ void importFile(int deviceId, int storageId, ParcelFileDescriptor target) throws IOException {
+ final String key = pack(deviceId, storageId);
+ if (mImportFileBytes.containsKey(key)) {
+ try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(target)) {
+ outputStream.write(mImportFileBytes.get(key));
+ }
} else {
- throw new IOException("getObject error: " + key);
+ throw new IOException("importFile error: " + key);
+ }
+ }
+
+ @Override
+ byte[] getThumbnail(int deviceId, int objectHandle) throws IOException {
+ final String key = pack(deviceId, objectHandle);
+ if (mThumbnailBytes.containsKey(key)) {
+ return mThumbnailBytes.get(key);
+ } else {
+ throw new IOException("getThumbnail error: " + key);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 348d0ec..632a867 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -151,7 +151,7 @@
mScanResultCache.put(result.BSSID, result);
}
}
- update(mInfo, mNetworkInfo);
+ update(mConfig, mInfo, mNetworkInfo);
mRssi = getRssi();
mSeen = getSeen();
}
@@ -629,11 +629,18 @@
return mConfig != null && mConfig.isPasspoint();
}
- /** Return whether the given {@link WifiInfo} is for this access point. */
- private boolean isInfoForThisAccessPoint(WifiInfo info) {
+ /**
+ * Return whether the given {@link WifiInfo} is for this access point.
+ * If the current AP does not have a network Id then the config is used to
+ * match based on SSID and security.
+ */
+ private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) {
if (isPasspoint() == false && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
return networkId == info.getNetworkId();
- } else {
+ } else if (config != null) {
+ return matches(config);
+ }
+ else {
// Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
// (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
// TODO: Handle hex string SSIDs.
@@ -705,7 +712,7 @@
}
boolean update(ScanResult result) {
- if (ssid.equals(result.SSID) && security == getSecurity(result)) {
+ if (matches(result)) {
/* Update the LRU timestamp, if BSSID exists */
mScanResultCache.get(result.BSSID);
@@ -735,9 +742,9 @@
return false;
}
- boolean update(WifiInfo info, NetworkInfo networkInfo) {
+ boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
boolean reorder = false;
- if (info != null && isInfoForThisAccessPoint(info)) {
+ if (info != null && isInfoForThisAccessPoint(config, info)) {
reorder = (mInfo == null);
mRssi = info.getRssi();
mInfo = info;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 33f993e..c28288e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -281,6 +281,19 @@
return mScanResultCache.values();
}
+ private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
+ final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+ if (configs != null) {
+ for (WifiConfiguration config : configs) {
+ if (mLastInfo != null && networkId == config.networkId &&
+ !(config.selfAdded && config.numAssociation == 0)) {
+ return config;
+ }
+ }
+ }
+ return null;
+ }
+
private void updateAccessPoints() {
// Swap the current access points into a cached list.
List<AccessPoint> cachedAccessPoints = getAccessPoints();
@@ -295,21 +308,21 @@
* correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */
Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
WifiConfiguration connectionConfig = null;
+ if (mLastInfo != null) {
+ connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
+ }
final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
mSavedNetworksExist = configs.size() != 0;
for (WifiConfiguration config : configs) {
- if (mLastInfo != null && mLastInfo.getNetworkId() == config.networkId) {
- connectionConfig = config;
- }
if (config.selfAdded && config.numAssociation == 0) {
continue;
}
AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
if (config.isPasspoint() == false) {
- accessPoint.update(mLastInfo, mLastNetworkInfo);
+ accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
}
if (mIncludeSaved) {
@@ -346,7 +359,7 @@
if (!found && mIncludeScans) {
AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
if (mLastInfo != null && mLastNetworkInfo != null) {
- accessPoint.update(mLastInfo, mLastNetworkInfo);
+ accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
}
if (result.isPasspointNetwork()) {
@@ -437,9 +450,14 @@
mLastNetworkInfo = networkInfo;
}
+ WifiConfiguration connectionConfig = null;
+ if (mLastInfo != null) {
+ connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
+ }
+
boolean reorder = false;
for (int i = mAccessPoints.size() - 1; i >= 0; --i) {
- if (mAccessPoints.get(i).update(mLastInfo, mLastNetworkInfo)) {
+ if (mAccessPoints.get(i).update(connectionConfig, mLastInfo, mLastNetworkInfo)) {
reorder = true;
}
}
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 477d9d7..2168e8b 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -63,4 +63,4 @@
android:background="@drawable/recents_button_bg"
android:visibility="invisible"
android:src="@drawable/recents_dismiss_light" />
-</com.android.systemui.recents.views.TaskViewHeader>
\ No newline at end of file
+</com.android.systemui.recents.views.TaskViewHeader>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 789457d..b47fb30 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -419,8 +419,10 @@
updateRecentsTasks();
// If this is a new instance from a configuration change, then we have to manually trigger
- // the enter animation state
- if (mConfig.launchedHasConfigurationChanged) {
+ // the enter animation state, or if recents was relaunched by AM, without going through
+ // the normal mechanisms
+ boolean wasLaunchedByAm = !mConfig.launchedFromHome && !mConfig.launchedFromAppWithThumbnail;
+ if (mConfig.launchedHasConfigurationChanged || wasLaunchedByAm) {
onEnterAnimationTriggered();
}
@@ -454,6 +456,16 @@
// Unregister any broadcast receivers for the task loader
loader.unregisterReceivers();
+
+ // Workaround for b/22542869, if the RecentsActivity is started again, but without going
+ // through SystemUI, we need to reset the config launch flags to ensure that we do not
+ // wait on the system to send a signal that was never queued.
+ mConfig.launchedFromHome = false;
+ mConfig.launchedFromSearchHome = false;
+ mConfig.launchedFromAppWithThumbnail = false;
+ mConfig.launchedToTaskId = -1;
+ mConfig.launchedWithAltTab = false;
+ mConfig.launchedHasConfigurationChanged = false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
index 71ee4e4..41d30c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java
@@ -21,11 +21,13 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
@@ -71,6 +73,7 @@
private static NavigationBarAppsModel sAppsModel;
private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
private final LayoutInflater mLayoutInflater;
// This view has two roles:
@@ -82,13 +85,26 @@
// When the user is not dragging this member is null.
private View mDragView;
+ private long mCurrentUserSerialNumber = -1;
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ int currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ onUserSwitched(currentUserId);
+ }
+ }
+ };
+
public NavigationBarApps(Context context, AttributeSet attrs) {
super(context, attrs);
if (sAppsModel == null) {
sAppsModel = new NavigationBarAppsModel(context);
- sAppsModel.initialize(); // Load the saved icons, if any.
}
mPackageManager = context.getPackageManager();
+ mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mLayoutInflater = LayoutInflater.from(context);
// Dragging an icon removes and adds back the dragged icon. Use the layout transitions to
@@ -108,17 +124,32 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- // When an icon is dragged out of the pinned area this view's width changes, which causes
- // the parent container's layout to change and the divider and recents icons to shift left.
- // Animate the parent's CHANGING transition.
- ViewGroup parent = (ViewGroup) getParent();
- LayoutTransition transition = new LayoutTransition();
- transition.disableTransitionType(LayoutTransition.APPEARING);
- transition.disableTransitionType(LayoutTransition.DISAPPEARING);
- transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
- transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
- transition.enableTransitionType(LayoutTransition.CHANGING);
- parent.setLayoutTransition(transition);
+ // When an icon is dragged out of the pinned area this view's width changes, which causes
+ // the parent container's layout to change and the divider and recents icons to shift left.
+ // Animate the parent's CHANGING transition.
+ ViewGroup parent = (ViewGroup) getParent();
+ LayoutTransition transition = new LayoutTransition();
+ transition.disableTransitionType(LayoutTransition.APPEARING);
+ transition.disableTransitionType(LayoutTransition.DISAPPEARING);
+ transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+ transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+ transition.enableTransitionType(LayoutTransition.CHANGING);
+ parent.setLayoutTransition(transition);
+
+ mCurrentUserSerialNumber = mUserManager.getSerialNumberForUser(
+ new UserHandle(ActivityManager.getCurrentUser()));
+ sAppsModel.setCurrentUser(mCurrentUserSerialNumber);
+ recreateAppButtons();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mContext.unregisterReceiver(mBroadcastReceiver);
}
/**
@@ -433,14 +464,11 @@
AppInfo appInfo = sAppsModel.getApp(indexOfChild(v));
ComponentName component = appInfo.getComponentName();
- UserManager userManager =
- (UserManager) getContext().getSystemService(Context.USER_SERVICE);
-
long appUserSerialNumber = appInfo.getUserSerialNumber();
UserHandle appUser = null;
if (appUserSerialNumber != AppInfo.USER_UNSPECIFIED) {
- appUser = userManager.getUserForSerialNumber(appUserSerialNumber);
+ appUser = mUserManager.getUserForSerialNumber(appUserSerialNumber);
}
int appUserId;
@@ -510,4 +538,15 @@
Log.e(TAG, "Attempt to launch activity without category Intent.CATEGORY_LAUNCHER " + component);
}
}
+
+ private void onUserSwitched(int currentUserId) {
+ final long newUserSerialNumber =
+ mUserManager.getSerialNumberForUser(new UserHandle(currentUserId));
+
+ if (newUserSerialNumber != mCurrentUserSerialNumber) {
+ mCurrentUserSerialNumber = newUserSerialNumber;
+ sAppsModel.setCurrentUser(newUserSerialNumber);
+ recreateAppButtons();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
index 9f80d33..39f1304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarAppsModel.java
@@ -19,16 +19,20 @@
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.os.UserHandle;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Data model and controller for app icons appearing in the navigation bar. The data is stored on
@@ -48,7 +52,7 @@
private final static String VERSION_PREF = "version";
// Current version number for preferences.
- private final static int CURRENT_VERSION = 1;
+ private final static int CURRENT_VERSION = 2;
// Preference name for the number of app icons.
private final static String APP_COUNT_PREF = "app_count";
@@ -59,42 +63,99 @@
// User serial number prefix for each app's info. The actual pref has an integer appended to it.
private final static String APP_USER_PREFIX = "app_user_";
- private final LauncherApps mLauncherApps;
+ // Character separating current user serial number from the user-specific part of a pref.
+ // Example "22|app_user_2" - when logged as user with serial 22, we'll use this pref for the
+ // user serial of the third app of the logged-in user.
+ private final static char USER_SEPARATOR = '|';
+
+ final Context mContext;
private final SharedPreferences mPrefs;
// Apps are represented as an ordered list of app infos.
private final List<AppInfo> mApps = new ArrayList<AppInfo>();
- public NavigationBarAppsModel(Context context) {
- mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
- mPrefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
- }
+ // Serial number of the current user.
+ private long mCurrentUserSerialNumber = AppInfo.USER_UNSPECIFIED;
- @VisibleForTesting
- NavigationBarAppsModel(LauncherApps launcherApps, SharedPreferences prefs) {
- mLauncherApps = launcherApps;
- mPrefs = prefs;
+ public NavigationBarAppsModel(Context context) {
+ mContext = context;
+ mPrefs = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+
+ int version = mPrefs.getInt(VERSION_PREF, -1);
+ if (version != CURRENT_VERSION) {
+ // Since the data format changed, clean everything.
+ SharedPreferences.Editor edit = mPrefs.edit();
+ edit.clear();
+ edit.putInt(VERSION_PREF, CURRENT_VERSION);
+ edit.apply();
+ }
}
/**
- * Initializes the model with a list of apps, either by loading it off disk or by supplying
- * a default list.
+ * Reinitializes the model for a new user.
*/
- public void initialize() {
- if (mApps.size() > 0) {
- Slog.e(TAG, "Model already initialized");
- return;
- }
+ public void setCurrentUser(long userSerialNumber) {
+ mCurrentUserSerialNumber = userSerialNumber;
- // Check for an existing list of apps.
- int version = mPrefs.getInt(VERSION_PREF, -1);
- if (version == CURRENT_VERSION) {
+ mApps.clear();
+
+ int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1);
+ if (appCount >= 0) {
loadAppsFromPrefs();
} else {
+ // We switched to this user for the first time ever. This is a good opportunity to clean
+ // prefs for users deleted in the past.
+ removePrefsForDeletedUsers();
+
addDefaultApps();
}
}
+ /**
+ * Removes prefs for users that don't exist on the device.
+ */
+ private void removePrefsForDeletedUsers() {
+ UserManager userManager =
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ // Build a set of string representations of serial numbers of the device users.
+ final List<UserInfo> users = userManager.getUsers();
+ final int userCount = users.size();
+
+ final Set<String> userSerials = new HashSet<String> ();
+
+ for (int i = 0; i < userCount; ++i) {
+ userSerials.add(Long.toString(users.get(i).serialNumber));
+ }
+
+ // Walk though all prefs and delete ones which user is not in the string set.
+ final Map<String, ?> allPrefs = mPrefs.getAll();
+ final SharedPreferences.Editor edit = mPrefs.edit();
+
+ for (Map.Entry<String, ?> pref : allPrefs.entrySet()) {
+ final String key = pref.getKey();
+ if (key.equals(VERSION_PREF)) continue;
+
+ final int userSeparatorPos = key.indexOf(USER_SEPARATOR);
+
+ if (userSeparatorPos < 0) {
+ // Removing anomalous pref with no user.
+ edit.remove(key);
+ continue;
+ }
+
+ final String prefUserSerial = key.substring(0, userSeparatorPos);
+
+ if (!userSerials.contains(prefUserSerial)) {
+ // Removes pref for a not existing user.
+ edit.remove(key);
+ continue;
+ }
+ }
+
+ edit.apply();
+ }
+
/** Returns the number of apps. */
public int getAppCount() {
return mApps.size();
@@ -123,11 +184,8 @@
/** Saves the current model to disk. */
public void savePrefs() {
SharedPreferences.Editor edit = mPrefs.edit();
- // The user might have removed icons, so clear all the old prefs.
- edit.clear();
- edit.putInt(VERSION_PREF, CURRENT_VERSION);
int appCount = mApps.size();
- edit.putInt(APP_COUNT_PREF, appCount);
+ edit.putInt(userPrefixed(APP_COUNT_PREF), appCount);
for (int i = 0; i < appCount; i++) {
final AppInfo appInfo = mApps.get(i);
String componentNameString = appInfo.getComponentName().flattenToString();
@@ -140,7 +198,7 @@
/** Loads the list of apps from SharedPreferences. */
private void loadAppsFromPrefs() {
- int appCount = mPrefs.getInt(APP_COUNT_PREF, -1);
+ int appCount = mPrefs.getInt(userPrefixed(APP_COUNT_PREF), -1);
for (int i = 0; i < appCount; i++) {
String prefValue = mPrefs.getString(prefNameForApp(i), null);
if (prefValue == null) {
@@ -154,27 +212,48 @@
}
}
+ @VisibleForTesting
+ protected int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
/** Adds the first few apps from the owner profile. Used for demo purposes. */
private void addDefaultApps() {
// Get a list of all app activities.
- List<LauncherActivityInfo> apps =
- mLauncherApps.getActivityList(
- null /* packageName */, new UserHandle(ActivityManager.getCurrentUser()));
- int appCount = apps.size();
+ final Intent queryIntent = new Intent(Intent.ACTION_MAIN, null);
+ queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ final int currentUser = getCurrentUser();
+
+ final List<ResolveInfo> apps = mContext.getPackageManager().queryIntentActivitiesAsUser(
+ queryIntent, 0 /* flags */, currentUser);
+ final int appCount = apps.size();
for (int i = 0; i < NUM_INITIAL_APPS && i < appCount; i++) {
- LauncherActivityInfo activityInfo = apps.get(i);
- mApps.add(new AppInfo(activityInfo.getComponentName(), AppInfo.USER_UNSPECIFIED));
+ ResolveInfo ri = apps.get(i);
+ ComponentName componentName = new ComponentName(
+ ri.activityInfo.packageName, ri.activityInfo.name);
+ mApps.add(new AppInfo(componentName, AppInfo.USER_UNSPECIFIED));
}
+
+ savePrefs();
+ }
+
+ /** Returns a pref prefixed with the serial number of the current user. */
+ private String userPrefixed(String pref) {
+ if (mCurrentUserSerialNumber == AppInfo.USER_UNSPECIFIED) {
+ throw new RuntimeException("Current user is not yet set");
+ }
+
+ return Long.toString(mCurrentUserSerialNumber) + USER_SEPARATOR + pref;
}
/** Returns the pref name for the app at a given index. */
- private static String prefNameForApp(int index) {
- return APP_PREF_PREFIX + Integer.toString(index);
+ private String prefNameForApp(int index) {
+ return userPrefixed(APP_PREF_PREFIX + Integer.toString(index));
}
/** Returns the pref name for the app's user at a given index. */
- private static String prefUserForApp(int index) {
- return APP_USER_PREFIX + Integer.toString(index);
+ private String prefUserForApp(int index) {
+ return userPrefixed(APP_USER_PREFIX + Integer.toString(index));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5d64abb9..2253cad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1076,6 +1076,7 @@
if (shouldDisableNavbarGestures()) {
return false;
}
+ MetricsLogger.action(mContext, MetricsLogger.ACTION_ASSIST_LONG_PRESS);
mAssistManager.startAssist(new Bundle() /* args */);
awakenDreams();
if (mNavigationBarView != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
index f46e76c..31007ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarAppsModelTest.java
@@ -18,24 +18,35 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.os.UserHandle;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
import android.test.AndroidTestCase;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/** Tests for the data model for the navigation bar app icons. */
public class NavigationBarAppsModelTest extends AndroidTestCase {
- private LauncherApps mMockLauncherApps;
+ private PackageManager mMockPackageManager;
private SharedPreferences mMockPrefs;
+ private SharedPreferences.Editor mMockEdit;
+ private UserManager mMockUserManager;
private NavigationBarAppsModel mModel;
@@ -47,26 +58,44 @@
System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
- mMockLauncherApps = mock(LauncherApps.class);
+ final Context context = mock(Context.class);
+ mMockPackageManager = mock(PackageManager.class);
mMockPrefs = mock(SharedPreferences.class);
- mModel = new NavigationBarAppsModel(mMockLauncherApps, mMockPrefs);
+ mMockEdit = mock(SharedPreferences.Editor.class);
+ mMockUserManager = mock(UserManager.class);
+
+ when (context.getSharedPreferences(
+ "com.android.systemui.navbarapps", Context.MODE_PRIVATE)).thenReturn(mMockPrefs);
+ when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+ when(context.getPackageManager()).thenReturn(mMockPackageManager);
+
+ setContext(context);
+
+ when(mMockUserManager.getUsers()).thenReturn(new ArrayList<UserInfo>());
+ // Assume the version pref is present and equal to the current version.
+ when(mMockPrefs.getInt("version", -1)).thenReturn(2);
+ when(mMockPrefs.edit()).thenReturn(mMockEdit);
+
+ mModel = new NavigationBarAppsModel(context) {
+ @Override
+ protected int getCurrentUser() {
+ return 0;
+ }
+ };
}
/** Initializes the model from SharedPreferences for a few app activites. */
private void initializeModelFromPrefs() {
- // Assume the version pref is present.
- when(mMockPrefs.getInt("version", -1)).thenReturn(1);
-
// Assume several apps are stored.
- when(mMockPrefs.getInt("app_count", -1)).thenReturn(3);
- when(mMockPrefs.getString("app_0", null)).thenReturn("package0/class0");
- when(mMockPrefs.getLong("app_user_0", -1)).thenReturn(-1L);
- when(mMockPrefs.getString("app_1", null)).thenReturn("package1/class1");
- when(mMockPrefs.getLong("app_user_1", -1)).thenReturn(45L);
- when(mMockPrefs.getString("app_2", null)).thenReturn("package2/class2");
- when(mMockPrefs.getLong("app_user_2", -1)).thenReturn(239L);
+ when(mMockPrefs.getInt("22|app_count", -1)).thenReturn(3);
+ when(mMockPrefs.getString("22|app_0", null)).thenReturn("package0/class0");
+ when(mMockPrefs.getLong("22|app_user_0", -1)).thenReturn(-1L);
+ when(mMockPrefs.getString("22|app_1", null)).thenReturn("package1/class1");
+ when(mMockPrefs.getLong("22|app_user_1", -1)).thenReturn(45L);
+ when(mMockPrefs.getString("22|app_2", null)).thenReturn("package2/class2");
+ when(mMockPrefs.getLong("22|app_user_2", -1)).thenReturn(239L);
- mModel.initialize();
+ mModel.setCurrentUser(22L);
}
/** Tests initializing the model from SharedPreferences. */
@@ -83,22 +112,26 @@
/** Tests initializing the model when the SharedPreferences aren't available. */
public void testInitializeDefaultApps() {
- // Assume the version pref isn't available.
- when(mMockPrefs.getInt("version", -1)).thenReturn(-1);
+ // Assume the user's app count pref isn't available.
+ when(mMockPrefs.getInt("0|app_count", -1)).thenReturn(-1);
// Assume some installed activities.
- LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
- when(activity1.getComponentName()).thenReturn(new ComponentName("package1", "class1"));
- LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
- when(activity2.getComponentName()).thenReturn(new ComponentName("package2", "class2"));
- List<LauncherActivityInfo> apps = new ArrayList<LauncherActivityInfo>();
- apps.add(activity1);
- apps.add(activity2);
- when(mMockLauncherApps.getActivityList(anyString(), any(UserHandle.class)))
- .thenReturn(apps);
+ ActivityInfo ai1 = new ActivityInfo();
+ ai1.packageName = "package1";
+ ai1.name = "class1";
+ ActivityInfo ai2 = new ActivityInfo();
+ ai2.packageName = "package2";
+ ai2.name = "class2";
+ ResolveInfo ri1 = new ResolveInfo();
+ ri1.activityInfo = ai1;
+ ResolveInfo ri2 = new ResolveInfo();
+ ri2.activityInfo = ai2;
+ when(mMockPackageManager
+ .queryIntentActivitiesAsUser(any(Intent.class), eq(0), eq(0)))
+ .thenReturn(Arrays.asList(ri1, ri2));
- // Initializing the model should load the installed activities.
- mModel.initialize();
+ // Setting the user should load the installed activities.
+ mModel.setCurrentUser(0L);
assertEquals(2, mModel.getAppCount());
assertEquals("package1/class1", mModel.getApp(0).getComponentName().flattenToString());
assertEquals(-1L, mModel.getApp(0).getUserSerialNumber());
@@ -108,19 +141,16 @@
/** Tests initializing the model if one of the prefs is missing. */
public void testInitializeWithMissingPref() {
- // Assume the version pref is present.
- when(mMockPrefs.getInt("version", -1)).thenReturn(1);
-
// Assume two apps are nominally stored.
- when(mMockPrefs.getInt("app_count", -1)).thenReturn(2);
- when(mMockPrefs.getString("app_0", null)).thenReturn("package0/class0");
- when(mMockPrefs.getLong("app_user_0", -1)).thenReturn(239L);
+ when(mMockPrefs.getInt("22|app_count", -1)).thenReturn(2);
+ when(mMockPrefs.getString("22|app_0", null)).thenReturn("package0/class0");
+ when(mMockPrefs.getLong("22|app_user_0", -1)).thenReturn(239L);
// But assume one pref is missing.
- when(mMockPrefs.getString("app_1", null)).thenReturn(null);
+ when(mMockPrefs.getString("22|app_1", null)).thenReturn(null);
// Initializing the model should load from prefs and skip the missing one.
- mModel.initialize();
+ mModel.setCurrentUser(22L);
assertEquals(1, mModel.getAppCount());
assertEquals("package0/class0", mModel.getApp(0).getComponentName().flattenToString());
assertEquals(239L, mModel.getApp(0).getUserSerialNumber());
@@ -130,15 +160,64 @@
public void testSavePrefs() {
initializeModelFromPrefs();
- SharedPreferences.Editor mockEdit = mock(SharedPreferences.Editor.class);
- when(mMockPrefs.edit()).thenReturn(mockEdit);
-
mModel.savePrefs();
- verify(mockEdit).clear(); // Old prefs were removed.
- verify(mockEdit).putInt("version", 1);
- verify(mockEdit).putInt("app_count", 3);
- verify(mockEdit).putString("app_0", "package0/class0");
- verify(mockEdit).putString("app_1", "package1/class1");
- verify(mockEdit).putString("app_2", "package2/class2");
+ verify(mMockEdit).putInt("22|app_count", 3);
+ verify(mMockEdit).putString("22|app_0", "package0/class0");
+ verify(mMockEdit).putLong("22|app_user_0", -1L);
+ verify(mMockEdit).putString("22|app_1", "package1/class1");
+ verify(mMockEdit).putLong("22|app_user_1", 45L);
+ verify(mMockEdit).putString("22|app_2", "package2/class2");
+ verify(mMockEdit).putLong("22|app_user_2", 239L);
+ verify(mMockEdit).apply();
+ verifyNoMoreInteractions(mMockEdit);
+ }
+
+ /** Tests cleaning all prefs on a version change. */
+ public void testVersionChange() {
+ // Assume the version pref changed.
+ when(mMockPrefs.getInt("version", -1)).thenReturn(1);
+
+ new NavigationBarAppsModel(getContext());
+ verify(mMockEdit).clear();
+ verify(mMockEdit).putInt("version", 2);
+ verify(mMockEdit).apply();
+ verifyNoMoreInteractions(mMockEdit);
+ }
+
+ /** Tests cleaning prefs for deleted users. */
+ public void testCleaningDeletedUsers() {
+ // Users on the device.
+ final UserInfo user1 = new UserInfo(11, "", 0);
+ user1.serialNumber = 1111;
+ final UserInfo user2 = new UserInfo(13, "", 0);
+ user2.serialNumber = 1313;
+
+ when(mMockUserManager.getUsers()).thenReturn(Arrays.asList(user1, user2));
+
+ when(mMockPrefs.edit()).
+ thenReturn(mMockEdit).
+ thenReturn(mock(SharedPreferences.Editor.class));
+
+ // Assume the user's app count pref isn't available. This will trigger clearing deleted
+ // users' prefs.
+ when(mMockPrefs.getInt("0|app_count", -1)).thenReturn(-1);
+
+ final Map allPrefs = new HashMap<String, Object>();
+ allPrefs.put("version", null);
+ allPrefs.put("some_strange_pref", null);
+ allPrefs.put("", null);
+ allPrefs.put("|", null);
+ allPrefs.put("1313|app_count", null);
+ allPrefs.put("1212|app_count", null);
+ when(mMockPrefs.getAll()).thenReturn(allPrefs);
+
+ // Setting the user should remove prefs for deleted users.
+ mModel.setCurrentUser(0L);
+ verify(mMockEdit).remove("some_strange_pref");
+ verify(mMockEdit).remove("");
+ verify(mMockEdit).remove("|");
+ verify(mMockEdit).remove("1212|app_count");
+ verify(mMockEdit).apply();
+ verifyNoMoreInteractions(mMockEdit);
}
}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 2a3492b..45c020c 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -25,6 +25,7 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -509,6 +510,7 @@
}
}
+ String[] uidPackageNames = getPackagesForUid(uid);
ArrayMap<Callback, ArraySet<String>> callbackSpecs = null;
ArrayList<Callback> callbacks = mOpModeWatchers.get(code);
@@ -516,12 +518,13 @@
final int callbackCount = callbacks.size();
for (int i = 0; i < callbackCount; i++) {
Callback callback = callbacks.get(i);
+ ArraySet<String> changedPackages = new ArraySet<>();
+ Collections.addAll(changedPackages, uidPackageNames);
callbackSpecs = new ArrayMap<>();
- callbackSpecs.put(callback, null);
+ callbackSpecs.put(callback, changedPackages);
}
}
- String[] uidPackageNames = getPackagesForUid(uid);
for (String uidPackageName : uidPackageNames) {
callbacks = mPackageModeWatchers.get(uidPackageName);
if (callbacks != null) {
@@ -931,7 +934,6 @@
}
return noteOperationUnchecked(code, proxiedUid, proxiedPackageName,
Binder.getCallingUid(), proxyPackageName);
-
}
@Override
@@ -1266,7 +1268,7 @@
String tagName = parser.getName();
if (tagName.equals("pkg")) {
readPackage(parser);
- } if (tagName.equals("uid")) {
+ } else if (tagName.equals("uid")) {
readUidOps(parser);
} else {
Slog.w(TAG, "Unknown element under <app-ops>: "
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 64ee5f1..4e11070 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3555,7 +3555,7 @@
? new File(Environment.getDataDirectory(), SYSTEM_PATH)
: Environment.getUserSystemDirectory(userId);
final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
- if (!inputMethodDir.mkdirs()) {
+ if (!inputMethodDir.exists() && !inputMethodDir.mkdirs()) {
Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
}
final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 32fd56a..83e8db0 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -527,14 +527,14 @@
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot get secrets for accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -627,14 +627,14 @@
}
if (account == null) throw new IllegalArgumentException("account is null");
if (key == null) throw new IllegalArgumentException("key is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot get user data for accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -664,22 +664,32 @@
final long identityToken = clearCallingIdentity();
try {
- Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
- authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
- AuthenticatorDescription[] types =
- new AuthenticatorDescription[authenticatorCollection.size()];
- int i = 0;
- for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
- : authenticatorCollection) {
- types[i] = authenticator.type;
- i++;
- }
- return types;
+ return getAuthenticatorTypesInternal(userId);
+
} finally {
restoreCallingIdentity(identityToken);
}
}
+ /**
+ * Should only be called inside of a clearCallingIdentity block.
+ */
+ private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId) {
+ Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
+ authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
+ AuthenticatorDescription[] types =
+ new AuthenticatorDescription[authenticatorCollection.size()];
+ int i = 0;
+ for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
+ : authenticatorCollection) {
+ types[i] = authenticator.type;
+ i++;
+ }
+ return types;
+ }
+
+
+
private boolean isCrossUser(int callingUid, int userId) {
return (userId != UserHandle.getCallingUserId()
&& callingUid != Process.myUid()
@@ -697,7 +707,8 @@
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot explicitly add accounts of type: %s",
callingUid,
@@ -713,12 +724,10 @@
*/
// fails if the account already exists
- int uid = getCallingUid();
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- return addAccountInternal(accounts, account, password, extras, false, uid);
+ return addAccountInternal(accounts, account, password, extras, false, callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -794,25 +803,26 @@
if (account == null) {
throw new IllegalArgumentException("account is null");
}
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot notify authentication for accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = Binder.getCallingUserHandle().getIdentifier();
+
if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) {
return false;
}
- int user = UserHandle.getCallingUserId();
+
long identityToken = clearCallingIdentity();
try {
- UserAccounts accounts = getUserAccounts(user);
+ UserAccounts accounts = getUserAccounts(userId);
+ return updateLastAuthenticatedTime(account);
} finally {
restoreCallingIdentity(identityToken);
}
- return updateLastAuthenticatedTime(account);
}
private boolean updateLastAuthenticatedTime(Account account) {
@@ -985,8 +995,9 @@
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
if (features == null) throw new IllegalArgumentException("features is null");
- checkReadAccountsPermitted(callingUid, account.type);
int userId = UserHandle.getCallingUserId();
+ checkReadAccountsPermitted(callingUid, account.type, userId);
+
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1062,14 +1073,14 @@
+ ", pid " + Binder.getCallingPid());
}
if (accountToRename == null) throw new IllegalArgumentException("account is null");
- if (!isAccountManagedByCaller(accountToRename.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(accountToRename.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot rename accounts of type: %s",
callingUid,
accountToRename.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1211,14 +1222,15 @@
* authenticator. This will let users remove accounts (via Settings in the system) but not
* arbitrary applications (like competing authenticators).
*/
- if (!isAccountManagedByCaller(account.type, callingUid) && !isSystemUid(callingUid)) {
+ UserHandle user = new UserHandle(userId);
+ if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier())
+ && !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot remove accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
-
if (!canUserModifyAccounts(userId)) {
try {
response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
@@ -1235,10 +1247,7 @@
}
return;
}
-
- UserHandle user = new UserHandle(userId);
long identityToken = clearCallingIdentity();
-
UserAccounts accounts = getUserAccounts(userId);
cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
synchronized(accounts.credentialsPermissionNotificationIds) {
@@ -1268,6 +1277,7 @@
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
+ int userId = Binder.getCallingUserHandle().getIdentifier();
if (account == null) {
/*
* Null accounts should result in returning false, as per
@@ -1275,22 +1285,18 @@
*/
Log.e(TAG, "account is null");
return false;
- } else if (!isAccountManagedByCaller(account.type, callingUid)) {
+ } else if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot explicitly add accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
-
UserAccounts accounts = getUserAccountsForCaller();
- int userId = Binder.getCallingUserHandle().getIdentifier();
if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) {
return false;
}
-
logRecord(accounts, DebugDbHelper.ACTION_CALLED_ACCOUNT_REMOVE, TABLE_ACCOUNTS);
-
long identityToken = clearCallingIdentity();
try {
return removeAccountInternal(accounts, account);
@@ -1524,14 +1530,14 @@
}
if (account == null) throw new IllegalArgumentException("account is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot peek the authtokens associated with accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1552,14 +1558,14 @@
}
if (account == null) throw new IllegalArgumentException("account is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot set auth tokens associated with accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1578,14 +1584,14 @@
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot set secrets for accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1642,14 +1648,14 @@
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot clear passwords for accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1670,14 +1676,14 @@
}
if (key == null) throw new IllegalArgumentException("key is null");
if (account == null) throw new IllegalArgumentException("account is null");
- if (!isAccountManagedByCaller(account.type, callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(account.type, callingUid, userId)) {
String msg = String.format(
"uid %s cannot set user data for accounts of type: %s",
callingUid,
account.type);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -1840,8 +1846,8 @@
// skip the check if customTokens
final int callerUid = Binder.getCallingUid();
- final boolean permissionGranted = customTokens ||
- permissionIsGranted(account, authTokenType, callerUid);
+ final boolean permissionGranted =
+ customTokens || permissionIsGranted(account, authTokenType, callerUid, userId);
// Get the calling package. We will use it for the purpose of caching.
final String callerPkg = loginOptions.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME);
@@ -2363,14 +2369,14 @@
}
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (!isAccountManagedByCaller(accountType, callingUid) && !isSystemUid(callingUid)) {
+ int userId = UserHandle.getCallingUserId();
+ if (!isAccountManagedByCaller(accountType, callingUid, userId) && !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot edit authenticator properites for account type: %s",
callingUid,
accountType);
throw new SecurityException(msg);
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -2493,20 +2499,22 @@
}
/**
- * Returns the accounts for a specific user
+ * Returns the accounts visible to the client within the context of a specific user
* @hide
*/
public Account[] getAccounts(int userId) {
int callingUid = Binder.getCallingUid();
- if (!isReadAccountsPermitted(callingUid, null)) {
+ List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId);
+ if (visibleAccountTypes.isEmpty()) {
return new Account[0];
}
long identityToken = clearCallingIdentity();
try {
- UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, null, callingUid, null);
- }
+ return getAccountsInternal(
+ userId,
+ callingUid,
+ null, // packageName
+ visibleAccountTypes);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -2588,22 +2596,52 @@
callingUid = packageUid;
}
- // Authenticators should be able to see their own accounts regardless of permissions.
- if (TextUtils.isEmpty(type) && !isReadAccountsPermitted(callingUid, type)) {
+ List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId);
+ if (visibleAccountTypes.isEmpty()
+ || (type != null && !visibleAccountTypes.contains(type))) {
return new Account[0];
- }
+ } else if (visibleAccountTypes.contains(type)) {
+ // Prune the list down to just the requested type.
+ visibleAccountTypes = new ArrayList<>();
+ visibleAccountTypes.add(type);
+ } // else aggregate all the visible accounts (it won't matter if the list is empty).
long identityToken = clearCallingIdentity();
try {
- UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, type, callingUid, callingPackage);
- }
+ return getAccountsInternal(
+ userId,
+ callingUid,
+ callingPackage,
+ visibleAccountTypes);
} finally {
restoreCallingIdentity(identityToken);
}
}
+ private Account[] getAccountsInternal(
+ int userId,
+ int callingUid,
+ String callingPackage,
+ List<String> visibleAccountTypes) {
+ UserAccounts accounts = getUserAccounts(userId);
+ synchronized (accounts.cacheLock) {
+ UserAccounts userAccounts = getUserAccounts(userId);
+ ArrayList<Account> visibleAccounts = new ArrayList<>();
+ for (String visibleType : visibleAccountTypes) {
+ Account[] accountsForType = getAccountsFromCacheLocked(
+ userAccounts, visibleType, callingUid, callingPackage);
+ if (accountsForType != null) {
+ visibleAccounts.addAll(Arrays.asList(accountsForType));
+ }
+ }
+ Account[] result = new Account[visibleAccounts.size()];
+ for (int i = 0; i < visibleAccounts.size(); i++) {
+ result[i] = visibleAccounts.get(i);
+ }
+ return result;
+ }
+ }
+
@Override
public boolean addSharedAccountAsUser(Account account, int userId) {
userId = handleIncomingUser(userId);
@@ -2739,8 +2777,12 @@
}
if (response == null) throw new IllegalArgumentException("response is null");
if (type == null) throw new IllegalArgumentException("accountType is null");
- if (!isReadAccountsPermitted(callingUid, type)) {
+ int userId = UserHandle.getCallingUserId();
+
+ List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId);
+ if (!visibleAccountTypes.contains(type)) {
Bundle result = new Bundle();
+ // Need to return just the accounts that are from matching signatures.
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, new Account[0]);
try {
response.onResult(result);
@@ -2749,7 +2791,6 @@
}
return;
}
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
UserAccounts userAccounts = getUserAccounts(userId);
@@ -2763,7 +2804,11 @@
onResult(response, result);
return;
}
- new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features,
+ new GetAccountsByTypeAndFeatureSession(
+ userAccounts,
+ response,
+ type,
+ features,
callingUid).bind();
} finally {
restoreCallingIdentity(identityToken);
@@ -3696,10 +3741,11 @@
return false;
}
- private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+ private boolean permissionIsGranted(
+ Account account, String authTokenType, int callerUid, int userId) {
final boolean isPrivileged = isPrivileged(callerUid);
final boolean fromAuthenticator = account != null
- && isAccountManagedByCaller(account.type, callerUid);
+ && isAccountManagedByCaller(account.type, callerUid, userId);
final boolean hasExplicitGrants = account != null
&& hasExplicitlyGrantedPermission(account, authTokenType, callerUid);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -3711,23 +3757,45 @@
return fromAuthenticator || hasExplicitGrants || isPrivileged;
}
- private boolean isAccountManagedByCaller(String accountType, int callingUid) {
+ private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId) {
if (accountType == null) {
return false;
+ } else {
+ return getTypesVisibleToCaller(callingUid, userId).contains(accountType);
}
- final int callingUserId = UserHandle.getUserId(callingUid);
+ }
+
+ private boolean isAccountManagedByCaller(String accountType, int callingUid, int userId) {
+ if (accountType == null) {
+ return false;
+ } else {
+ return getTypesManagedByCaller(callingUid, userId).contains(accountType);
+ }
+ }
+
+ private List<String> getTypesVisibleToCaller(int callingUid, int userId) {
+ boolean isPermitted =
+ isPermitted(callingUid, Manifest.permission.GET_ACCOUNTS,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
+ Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted));
+ return getTypesForCaller(callingUid, userId, isPermitted);
+ }
+
+ private List<String> getTypesManagedByCaller(int callingUid, int userId) {
+ return getTypesForCaller(callingUid, userId, false);
+ }
+
+ private List<String> getTypesForCaller(
+ int callingUid, int userId, boolean isOtherwisePermitted) {
+ List<String> managedAccountTypes = new ArrayList<>();
for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
- mAuthenticatorCache.getAllServices(callingUserId)) {
- if (serviceInfo.type.type.equals(accountType)) {
- /*
- * We can't simply compare uids because uids can be recycled before the
- * authenticator cache is updated.
- */
- final int sigChk = mPackageManager.checkSignatures(serviceInfo.uid, callingUid);
- return sigChk == PackageManager.SIGNATURE_MATCH;
+ mAuthenticatorCache.getAllServices(userId)) {
+ final int sigChk = mPackageManager.checkSignatures(serviceInfo.uid, callingUid);
+ if (isOtherwisePermitted || sigChk == PackageManager.SIGNATURE_MATCH) {
+ managedAccountTypes.add(serviceInfo.type.type);
}
}
- return false;
+ return managedAccountTypes;
}
private boolean isAccountPresentForCaller(String accountName, String accountType) {
@@ -3792,28 +3860,12 @@
return false;
}
- private boolean isReadAccountsPermitted(int callingUid, String accountType) {
- /*
- * Settings app (which is in the same uid as AcocuntManagerService), apps with the
- * GET_ACCOUNTS permission or authenticators that own the account type should be able to
- * access accounts of the specified account.
- */
- boolean isPermitted =
- isPermitted(callingUid, Manifest.permission.GET_ACCOUNTS,
- Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
- boolean isAccountManagedByCaller = isAccountManagedByCaller(accountType, callingUid);
- Log.w(TAG, String.format(
- "isReadAccountPermitted: isPermitted: %s, isAM: %s",
- isPermitted,
- isAccountManagedByCaller));
- return isPermitted || isAccountManagedByCaller;
- }
-
/** Succeeds if any of the specified permissions are granted. */
private void checkReadAccountsPermitted(
int callingUid,
- String accountType) {
- if (!isReadAccountsPermitted(callingUid, accountType)) {
+ String accountType,
+ int userId) {
+ if (!isAccountVisibleToCaller(accountType, callingUid, userId)) {
String msg = String.format(
"caller uid %s cannot access %s accounts",
callingUid,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7f4746e..147a5fd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4252,14 +4252,13 @@
* @param token The Binder token referencing the Activity we want to finish.
* @param resultCode Result code, if any, from this Activity.
* @param resultData Result data (Intent), if any, from this Activity.
- * @param finishTask Whether to finish the task associated with this Activity. Only applies to
- * the root Activity in the task.
+ * @param finishTask Whether to finish the task associated with this Activity.
*
* @return Returns true if the activity successfully finished, or false if it is still running.
*/
@Override
public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
- boolean finishTask) {
+ int finishTask) {
// Refuse possible leaked file descriptors
if (resultData != null && resultData.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -4306,7 +4305,8 @@
final long origId = Binder.clearCallingIdentity();
try {
boolean res;
- if (finishTask && r == rootR) {
+ if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
+ || (finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY && r == rootR)) {
// If requested, remove the task that is associated to this activity only if it
// was the root activity in the task. The result code and data is ignored
// because we don't support returning them across task boundaries.
@@ -8930,6 +8930,35 @@
}
@Override
+ public int getActivityStackId(IBinder token) throws RemoteException {
+ synchronized (this) {
+ ActivityStack stack = ActivityRecord.getStackLocked(token);
+ if (stack == null) {
+ throw new IllegalArgumentException(
+ "getActivityStackId: No stack for token=" + token);
+ }
+ return stack.mStackId;
+ }
+ }
+
+ @Override
+ public void moveActivityToStack(IBinder token, int stackId) throws RemoteException {
+ synchronized(this) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ throw new IllegalArgumentException(
+ "moveActivityToStack: No activity record matching token=" + token);
+ }
+ moveTaskToStack(r.task.taskId, stackId, true /*toTop*/);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @Override
public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
"moveTaskToStack()");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 2b3cc92..e083709 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -25,7 +25,9 @@
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import android.graphics.Rect;
import android.util.ArraySet;
+import android.view.IApplicationToken;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BatteryStatsImpl;
@@ -554,6 +556,10 @@
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch root " + r);
continue;
}
+ if (r.mActivityType != target.mActivityType) {
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch activity type");
+ continue;
+ }
final Intent taskIntent = task.intent;
final Intent affinityIntent = task.affinityIntent;
@@ -2157,11 +2163,7 @@
+ task, new RuntimeException("here").fillInStackTrace());
task.addActivityToTop(r);
r.putInHistory();
- mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
- r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0,
- r.userId, r.info.configChanges, task.voiceSession != null,
- r.mLaunchTaskBehind);
+ addAppToken(r, task);
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
}
@@ -2221,10 +2223,7 @@
: AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
mNoAnimActivities.remove(r);
}
- mWindowManager.addAppToken(task.mActivities.indexOf(r),
- r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
- r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
+ addAppToken(r, task);
boolean doShow = true;
if (newTask) {
// Even though this activity is starting fresh, we still need
@@ -2273,10 +2272,7 @@
} else {
// If this is the first activity, don't do any fancy animations,
// because there is nothing for it to animate on top of.
- mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
- r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
- r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
+ addAppToken(r, task);
ActivityOptions.abort(options);
options = null;
}
@@ -2390,8 +2386,7 @@
+ " out to new task " + target.task);
}
- final int targetTaskId = targetTask.taskId;
- mWindowManager.setAppTask(target.appToken, targetTaskId);
+ setAppTask(target, targetTask);
boolean noOptions = canMoveOptions;
final int start = replyChainEnd < 0 ? i : replyChainEnd;
@@ -2416,10 +2411,10 @@
p.setTask(targetTask, null);
targetTask.addActivityAtBottom(p);
- mWindowManager.setAppTask(p.appToken, targetTaskId);
+ setAppTask(p, targetTask);
}
- mWindowManager.moveTaskToBottom(targetTaskId);
+ mWindowManager.moveTaskToBottom(targetTask.taskId);
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
}
@@ -2558,7 +2553,7 @@
+ " callers=" + Debug.getCallers(3));
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p
+ " from " + srcPos + " in to resetting task " + task);
- mWindowManager.setAppTask(p.appToken, taskId);
+ setAppTask(p, task);
}
mWindowManager.moveTaskToTop(taskId);
if (VALIDATE_TOKENS) {
@@ -4420,6 +4415,30 @@
}
}
+ void addAppToken(ActivityRecord r, TaskRecord task) {
+ final Rect bounds = task.getLaunchBounds();
+ final Configuration config =
+ mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
+ r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
+ (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
+ r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind,
+ bounds);
+ if (config != null) {
+ task.updateOverrideConfiguration(config, bounds);
+ }
+ r.taskConfigOverride = task.mOverrideConfig;
+ }
+
+ private void setAppTask(ActivityRecord r, TaskRecord task) {
+ final Rect bounds = task.getLaunchBounds();
+ final Configuration config =
+ mWindowManager.setAppTask(r.appToken, task.taskId, task.getLaunchBounds());
+ if (config != null) {
+ task.updateOverrideConfiguration(config, bounds);
+ }
+ r.taskConfigOverride = task.mOverrideConfig;
+ }
+
public int getStackId() {
return mStackId;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 76bac91..4a0fd89 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,15 +17,7 @@
package com.android.server.am;
import static android.Manifest.permission.START_ANY_ACTIVITY;
-import static android.app.ActivityManager.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.HOME_STACK_ID;
-import static android.app.ActivityManager.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
-import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.ActivityManager.*;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1789,20 +1781,22 @@
return mFocusedStack;
}
+ // We first try to put the task in the first dynamic stack.
final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
stack = homeDisplayStacks.get(stackNdx);
- if (!stack.isHomeStack()) {
+ final boolean isDynamicStack = stack.mStackId >= FIRST_DYNAMIC_STACK_ID;
+ if (isDynamicStack) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Setting focused stack=" + stack);
return stack;
}
}
- // TODO (multi-window): Change to select task id based on if the task should on in
- // fullscreen, freefrom, or sid-by-side stack.
+ // If there is no suitable dynamic stack then we figure out which static stack to use.
stack = getStack(
- FULLSCREEN_WORKSPACE_STACK_ID,
+ task != null
+ ? task.getLaunchStackId(mFocusedStack) : FULLSCREEN_WORKSPACE_STACK_ID,
true /*createStaticStackIfNeeded*/,
true /*createOnTop*/);
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
@@ -2899,7 +2893,7 @@
Slog.wtf(TAG, "Task in WindowManager, but not in ActivityManager???");
continue;
}
- task.updateOverrideConfiguration(newTaskConfigs.get(i));
+ task.updateOverrideConfiguration(newTaskConfigs.get(i), bounds);
}
if (r != null) {
@@ -2924,16 +2918,20 @@
return;
}
- task.mBounds = new Rect(bounds);
-
if (!mWindowManager.isValidTaskId(task.taskId)) {
// Task doesn't exist in window manager yet (e.g. was restored from recents).
- // No need to do anything else until we add the task to window manager.
+ // All we can do for now is update the bounds so it can be used when the task is
+ // added to window manager.
+ task.mBounds = task.mLastNonFullscreenBounds = new Rect(bounds);
+ if (task.stack != null && task.stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
+ // re-restore the task so it can have the proper stack association.
+ restoreRecentTaskLocked(task);
+ }
return;
}
final Configuration overrideConfig = mWindowManager.resizeTask(task.taskId, bounds);
- if (task.updateOverrideConfiguration(overrideConfig)) {
+ if (task.updateOverrideConfiguration(overrideConfig, bounds)) {
ActivityRecord r = task.topRunningActivityLocked(null);
if (r != null) {
final ActivityStack stack = task.stack;
@@ -2972,13 +2970,21 @@
}
private boolean restoreRecentTaskLocked(TaskRecord task) {
- // TODO (multi-window): Change to select task id based on if the task should on in
- // fullscreen, freefrom, or sid-by-side stack.
- // Always put task for lean back device in home stack since they only have one stack,
- // else use the preferred stack ID to get the stack we should use if it already exists.
- ActivityStack stack = mLeanbackOnlyDevice ? mHomeStack :
- getStack(FULLSCREEN_WORKSPACE_STACK_ID,
- true /*createStaticStackIfNeeded*/, false /*createOnTop*/);
+ final int stackId =
+ mLeanbackOnlyDevice ? mHomeStack.mStackId : task.getLaunchStackId(mFocusedStack);
+ if (task.stack != null) {
+ // Task has already been restored once. See if we need to do anything more
+ if (task.stack.mStackId == stackId) {
+ // Nothing else to do since it is already restored in the right stack.
+ return true;
+ }
+ // Remove current stack association, so we can re-associate the task with the
+ // right stack below.
+ task.stack.removeTask(task, "restoreRecentTaskLocked", false /*notMoving*/);
+ }
+
+ ActivityStack stack =
+ getStack(stackId, true /*createStaticStackIfNeeded*/, false /*createOnTop*/);
if (stack == null) {
// What does this mean??? Not sure how we would get here...
@@ -2992,12 +2998,7 @@
"Added restored task=" + task + " to stack=" + stack);
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- mWindowManager.addAppToken(0, r.appToken, task.taskId, stack.mStackId,
- r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0,
- r.userId, r.info.configChanges, task.voiceSession != null,
- r.mLaunchTaskBehind);
+ stack.addAppToken(activities.get(activityNdx), task);
}
return true;
}
@@ -3015,6 +3016,15 @@
task.stack.removeTask(task, "moveTaskToStack", false /* notMoving */);
}
stack.addTask(task, toTop, true);
+
+ // Make sure the task has the appropriate bounds/size for the stack it is in.
+ if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
+ resizeTaskLocked(task, null);
+ } else if (stackId == FREEFORM_WORKSPACE_STACK_ID
+ && task.mBounds == null && task.mLastNonFullscreenBounds != null) {
+ resizeTaskLocked(task, task.mLastNonFullscreenBounds);
+ }
+
// The task might have already been running and its visibility needs to be synchronized with
// the visibility of the stack / windows.
stack.ensureActivitiesVisibleLocked(null, 0);
@@ -3819,6 +3829,7 @@
}
info.taskIds = taskIds;
info.taskNames = taskNames;
+ info.taskBounds = taskBounds;
return info;
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a892c7d..dd57f80 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.HOME_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
@@ -94,7 +97,7 @@
private static final String ATTR_CALLING_PACKAGE = "calling_package";
private static final String ATTR_RESIZEABLE = "resizeable";
private static final String ATTR_PRIVILEGED = "privileged";
- private static final String ATTR_BOUNDS = "bounds";
+ private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
@@ -207,6 +210,10 @@
// Bounds of the Task. null for fullscreen tasks.
Rect mBounds = null;
+ // Last non-fullscreen bounds the task was launched in or resized to.
+ // The information is persisted and used to determine the appropriate stack to launch the
+ // task into on restore.
+ Rect mLastNonFullscreenBounds = null;
Configuration mOverrideConfig = Configuration.EMPTY;
@@ -301,7 +308,7 @@
mCallingPackage = callingPackage;
mResizeable = resizeable;
mPrivileged = privileged;
- mBounds = bounds;
+ mBounds = mLastNonFullscreenBounds = bounds;
}
void touchActiveTime() {
@@ -799,7 +806,8 @@
}
boolean isLockTaskWhitelistedLocked() {
- if (mCallingPackage == null) {
+ String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
+ if (pkg == null) {
return false;
}
String[] packages = mService.mLockTaskPackages.get(userId);
@@ -807,7 +815,7 @@
return false;
}
for (int i = packages.length - 1; i >= 0; --i) {
- if (mCallingPackage.equals(packages[i])) {
+ if (pkg.equals(packages[i])) {
return true;
}
}
@@ -963,8 +971,9 @@
out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage);
out.attribute(null, ATTR_RESIZEABLE, String.valueOf(mResizeable));
out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
- if (mBounds != null) {
- out.attribute(null, ATTR_BOUNDS, mBounds.flattenToString());
+ if (mLastNonFullscreenBounds != null) {
+ out.attribute(
+ null, ATTR_NON_FULLSCREEN_BOUNDS, mLastNonFullscreenBounds.flattenToString());
}
if (affinityIntent != null) {
@@ -1084,7 +1093,7 @@
resizeable = Boolean.valueOf(attrValue);
} else if (ATTR_PRIVILEGED.equals(attrName)) {
privileged = Boolean.valueOf(attrValue);
- } else if (ATTR_BOUNDS.equals(attrName)) {
+ } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
bounds = Rect.unflattenFromString(attrValue);
} else {
Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
@@ -1155,16 +1164,53 @@
return task;
}
- boolean updateOverrideConfiguration(Configuration newConfig) {
+ boolean updateOverrideConfiguration(Configuration newConfig, Rect bounds) {
Configuration oldConfig = mOverrideConfig;
mOverrideConfig = (newConfig == null) ? Configuration.EMPTY : newConfig;
// We override the configuration only when the task's dimensions are different from the
// display. In this manner, we know that if the override configuration is empty, the task
// is necessarily fullscreen.
mFullscreen = Configuration.EMPTY.equals(mOverrideConfig);
+ if (mFullscreen) {
+ if (mBounds != null) {
+ mLastNonFullscreenBounds = mBounds;
+ }
+ mBounds = null;
+ } else {
+ mBounds = mLastNonFullscreenBounds = new Rect(bounds);
+ }
return !mOverrideConfig.equals(oldConfig);
}
+ /** Returns the stack that should be used to launch this task. */
+ int getLaunchStackId(ActivityStack focusStack) {
+ if (stack != null) {
+ // We are already in a stack silly...
+ return stack.mStackId;
+ }
+ if (isHomeTask()) {
+ return HOME_STACK_ID;
+ }
+ if (focusStack != null && focusStack.mStackId != HOME_STACK_ID) {
+ // Like it or not you are going in the focused stack!
+ return focusStack.mStackId;
+ }
+ if (mBounds != null || mLastNonFullscreenBounds != null) {
+ return FREEFORM_WORKSPACE_STACK_ID;
+ }
+ return FULLSCREEN_WORKSPACE_STACK_ID;
+ }
+
+ /** Returns the bounds that should be used to launch this task. */
+ Rect getLaunchBounds() {
+ if (stack == null
+ || stack.mStackId == HOME_STACK_ID
+ || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ return null;
+ }
+ return mLastNonFullscreenBounds;
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("userId="); pw.print(userId);
pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 31fa5c4..57d7758 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -88,7 +88,6 @@
private int mUser = UserHandle.USER_OWNER;
private ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
- private int mPreviousRingerMode = -1;
private boolean mEffectsSuppressed;
public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
@@ -236,7 +235,6 @@
}
pw.print(prefix); pw.print("mUser="); pw.println(mUser);
dump(pw, prefix, "mConfig", mConfig);
- pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
mFiltering.dump(pw, prefix);
mConditions.dump(pw, prefix);
@@ -357,6 +355,17 @@
Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
}
+ private int getPreviousRingerModeSetting() {
+ return Global.getInt(mContext.getContentResolver(),
+ Global.ZEN_MODE_RINGER_LEVEL, AudioManager.RINGER_MODE_NORMAL);
+ }
+
+ private void setPreviousRingerModeSetting(Integer previousRingerLevel) {
+ Global.putString(
+ mContext.getContentResolver(), Global.ZEN_MODE_RINGER_LEVEL,
+ previousRingerLevel == null ? null : Integer.toString(previousRingerLevel));
+ }
+
private boolean evaluateZenMode(String reason, boolean setRingerMode) {
if (DEBUG) Log.d(TAG, "evaluateZenMode");
final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
@@ -430,16 +439,15 @@
case Global.ZEN_MODE_NO_INTERRUPTIONS:
case Global.ZEN_MODE_ALARMS:
if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) {
- mPreviousRingerMode = ringerModeInternal;
+ setPreviousRingerModeSetting(ringerModeInternal);
newRingerModeInternal = AudioManager.RINGER_MODE_SILENT;
}
break;
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
case Global.ZEN_MODE_OFF:
if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) {
- newRingerModeInternal = mPreviousRingerMode != -1 ? mPreviousRingerMode
- : AudioManager.RINGER_MODE_NORMAL;
- mPreviousRingerMode = -1;
+ newRingerModeInternal = getPreviousRingerModeSetting();
+ setPreviousRingerModeSetting(null);
}
break;
}
@@ -593,7 +601,7 @@
&& mZenMode != Global.ZEN_MODE_ALARMS) {
newZen = Global.ZEN_MODE_ALARMS;
}
- mPreviousRingerMode = ringerModeOld;
+ setPreviousRingerModeSetting(ringerModeOld);
}
break;
case AudioManager.RINGER_MODE_VIBRATE:
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 3227ef8..71a2d59 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -34,6 +34,7 @@
import android.provider.ContactsContract;
import android.provider.MediaStore;
import android.provider.Telephony.Sms.Intents;
+import android.security.Credentials;
import android.util.ArraySet;
import android.util.Log;
@@ -298,6 +299,15 @@
grantRuntimePermissionsLPw(storagePackage, STORAGE_PERMISSIONS, userId);
}
+ // CertInstaller
+ Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
+ PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackageLPr(
+ certInstallerIntent, userId);
+ if (certInstallerPackage != null
+ && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
+ grantRuntimePermissionsLPw(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
// Dialer
if (dialerAppPackageNames == null) {
Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 16571ea..454cdcf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5258,9 +5258,11 @@
}
private boolean shouldDispatchInputWhenNonInteractive() {
- // Send events to keyguard while the screen is on.
- if (isKeyguardShowingAndNotOccluded() && mDisplay != null
- && mDisplay.getState() != Display.STATE_OFF) {
+ 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()) {
return true;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index b9e8851..2a12aac 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -579,8 +579,14 @@
private void clearUserHasAuthenticated(int userId) {
if (userId == UserHandle.USER_ALL) {
mUserHasAuthenticated.clear();
+ synchronized (mUserHasAuthenticatedSinceBoot) {
+ mUserHasAuthenticatedSinceBoot.clear();
+ }
} else {
mUserHasAuthenticated.put(userId, false);
+ synchronized (mUserHasAuthenticatedSinceBoot) {
+ mUserHasAuthenticatedSinceBoot.put(userId, false);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/FocusedTaskFrame.java b/services/core/java/com/android/server/wm/FocusedTaskFrame.java
deleted file mode 100644
index d8e1095..0000000
--- a/services/core/java/com/android/server/wm/FocusedTaskFrame.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
-import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.util.Slog;
-import android.view.Display;
-import android.view.Surface.OutOfResourcesException;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-
-import com.android.server.wm.WindowStateAnimator.SurfaceTrace;
-
-class FocusedTaskFrame {
- private static final String TAG = "FocusedTaskFrame";
- private static final boolean DEBUG = false;
- private static final int THICKNESS = 2;
- private static final float ALPHA = 0.3f;
-
- private final SurfaceControl mSurfaceControl;
- private final Surface mSurface = new Surface();
- private final Paint mInnerPaint = new Paint();
- private final Paint mOuterPaint = new Paint();
- private final Rect mBounds = new Rect();
- private final Rect mLastBounds = new Rect();
- private int mLayer = -1;
-
- public FocusedTaskFrame(Display display, SurfaceSession session) {
- SurfaceControl ctrl = null;
- try {
- if (DEBUG_SURFACE_TRACE) {
- ctrl = new SurfaceTrace(session, "FocusedTaskFrame",
- 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
- } else {
- ctrl = new SurfaceControl(session, "FocusedTaskFrame",
- 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
- }
- ctrl.setLayerStack(display.getLayerStack());
- ctrl.setAlpha(ALPHA);
- mSurface.copyFrom(ctrl);
- } catch (OutOfResourcesException e) {
- }
- mSurfaceControl = ctrl;
-
- mInnerPaint.setStyle(Paint.Style.STROKE);
- mInnerPaint.setStrokeWidth(THICKNESS);
- mInnerPaint.setColor(Color.WHITE);
- mOuterPaint.setStyle(Paint.Style.STROKE);
- mOuterPaint.setStrokeWidth(THICKNESS);
- mOuterPaint.setColor(Color.BLACK);
- }
-
- private void draw() {
- if (mLastBounds.isEmpty()) {
- // Currently unset. Set it.
- mLastBounds.set(mBounds);
- }
-
- if (DEBUG) Slog.i(TAG, "draw: mBounds=" + mBounds + " mLastBounds=" + mLastBounds);
-
- Canvas c = null;
- try {
- c = mSurface.lockCanvas(mLastBounds);
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Unable to lock canvas", e);
- } catch (Surface.OutOfResourcesException e) {
- Slog.e(TAG, "Unable to lock canvas", e);
- }
- if (c == null) {
- if (DEBUG) Slog.w(TAG, "Canvas is null...");
- return;
- }
-
- c.drawRect(0, 0, mBounds.width(), mBounds.height(), mOuterPaint);
- c.drawRect(THICKNESS, THICKNESS, mBounds.width() - THICKNESS, mBounds.height() - THICKNESS,
- mInnerPaint);
- if (DEBUG) Slog.w(TAG, "c.width=" + c.getWidth() + " c.height=" + c.getHeight()
- + " c.clip=" + c .getClipBounds());
- mSurface.unlockCanvasAndPost(c);
- mLastBounds.set(mBounds);
- }
-
- private void setupSurface(boolean visible) {
- if (mSurfaceControl == null) {
- return;
- }
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setupSurface");
- SurfaceControl.openTransaction();
- try {
- if (visible) {
- mSurfaceControl.setPosition(mBounds.left, mBounds.top);
- mSurfaceControl.setSize(mBounds.width(), mBounds.height());
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
- }
- } finally {
- SurfaceControl.closeTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setupSurface");
- }
- }
-
- void setVisibility(Task task) {
- if (task == null || task.isFullscreen()) {
- setupSurface(false);
- } else {
- task.getBounds(mBounds);
- setupSurface(true);
- if (!mBounds.equals(mLastBounds)) {
- draw();
- }
- }
- }
-
- // Note: caller responsible for being inside
- // Surface.openTransaction() / closeTransaction()
- void setLayer(int layer) {
- if (mLayer == layer) {
- return;
- }
- mLayer = layer;
- mSurfaceControl.setLayer(mLayer);
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 79527b7..3ff5be1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -76,12 +76,13 @@
// of creating a new object per fullscreen task on a display.
private static final SparseArray<DimLayer> sSharedFullscreenDimLayers = new SparseArray<>();
- Task(int taskId, TaskStack stack, int userId, WindowManagerService service) {
+ Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds) {
mTaskId = taskId;
mStack = stack;
mUserId = userId;
mService = service;
mOverrideConfig = Configuration.EMPTY;
+ setBounds(bounds);
}
DisplayContent getDisplayContent() {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index aef99bc..90d2593 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -148,8 +148,7 @@
void updateDisplayInfo() {
if (mDisplayContent != null) {
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
- mAnimationBackgroundSurface.setBounds(mTmpRect);
+ setBounds(mFullscreen ? null : mBounds);
for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
mTasks.get(taskNdx).updateDisplayInfo(mDisplayContent);
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b9740af..7578256 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -709,8 +709,6 @@
mService.scheduleAnimationLocked();
}
- mService.setFocusedTaskLayer();
-
if (mService.mWatermark != null) {
mService.mWatermark.drawIfNeeded();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 20e24bb..3d7b499 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -250,11 +250,6 @@
static final int LAYER_OFFSET_DIM = 1;
/**
- * FocusedTaskFrame layer is immediately above focused window.
- */
- static final int LAYER_OFFSET_FOCUSED_TASK = 1;
-
- /**
* Animation thumbnail is as far as possible below the window above
* the thumbnail (or in other words as far as possible above the window
* below it).
@@ -445,9 +440,6 @@
StrictModeFlash mStrictModeFlash;
CircularDisplayMask mCircularDisplayMask;
EmulatorDisplayOverlay mEmulatorDisplayOverlay;
- FocusedTaskFrame mFocusedTaskFrame;
-
- int mFocusedTaskLayer;
final float[] mTmpFloats = new float[9];
final Rect mTmpContentRect = new Rect();
@@ -990,8 +982,6 @@
SurfaceControl.openTransaction();
try {
createWatermarkInTransaction();
- mFocusedTaskFrame = new FocusedTaskFrame(
- getDefaultDisplayContentLocked().getDisplay(), mFxSession);
} finally {
SurfaceControl.closeTransaction();
}
@@ -3697,24 +3687,26 @@
Binder.restoreCallingIdentity(origId);
}
- private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken) {
+ private Task createTaskLocked(
+ int taskId, int stackId, int userId, AppWindowToken atoken, Rect bounds) {
if (DEBUG_STACK) Slog.i(TAG, "createTaskLocked: taskId=" + taskId + " stackId=" + stackId
- + " atoken=" + atoken);
+ + " atoken=" + atoken + " bounds=" + bounds);
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack == null) {
throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
}
EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
- Task task = new Task(taskId, stack, userId, this);
+ Task task = new Task(taskId, stack, userId, this, bounds);
mTaskIdToTask.put(taskId, task);
stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers);
return task;
}
@Override
- public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
+ public Configuration addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId,
- int configChanges, boolean voiceInteraction, boolean launchTaskBehind) {
+ int configChanges, boolean voiceInteraction, boolean launchTaskBehind,
+ Rect taskBounds) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3738,7 +3730,7 @@
AppWindowToken atoken = findAppWindowToken(token.asBinder());
if (atoken != null) {
Slog.w(TAG, "Attempted to add existing app token: " + token);
- return;
+ return null;
}
atoken = new AppWindowToken(this, token, voiceInteraction);
atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
@@ -3752,8 +3744,10 @@
+ " to stack=" + stackId + " task=" + taskId + " at " + addPos);
Task task = mTaskIdToTask.get(taskId);
+ Configuration outConfig = null;
if (task == null) {
- task = createTaskLocked(taskId, stackId, userId, atoken);
+ task = createTaskLocked(taskId, stackId, userId, atoken, taskBounds);
+ outConfig = task.mOverrideConfig;
}
task.addAppToken(addPos, atoken);
@@ -3763,12 +3757,12 @@
atoken.hidden = true;
atoken.hiddenRequested = true;
- //dump();
+ return outConfig;
}
}
@Override
- public void setAppTask(IBinder token, int taskId) {
+ public Configuration setAppTask(IBinder token, int taskId, Rect taskBounds) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppTask()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3778,17 +3772,20 @@
final AppWindowToken atoken = findAppWindowToken(token);
if (atoken == null) {
Slog.w(TAG, "Attempted to set task id of non-existing app token: " + token);
- return;
+ return null;
}
final Task oldTask = atoken.mTask;
oldTask.removeAppToken(atoken);
Task newTask = mTaskIdToTask.get(taskId);
+ Configuration outConfig = null;
if (newTask == null) {
- newTask =
- createTaskLocked(taskId, oldTask.mStack.mStackId, oldTask.mUserId, atoken);
+ newTask = createTaskLocked(
+ taskId, oldTask.mStack.mStackId, oldTask.mUserId, atoken, taskBounds);
+ outConfig = newTask.mOverrideConfig;
}
newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken);
+ return outConfig;
}
}
@@ -4074,34 +4071,14 @@
}
}
- /** Call while in a Surface transaction. */
- void setFocusedTaskLayer() {
- mFocusedTaskLayer = 0;
+ void setFocusTaskRegion() {
if (mFocusedApp != null) {
- final WindowList windows = mFocusedApp.allAppWindows;
- for (int i = windows.size() - 1; i >= 0; --i) {
- final WindowState win = windows.get(i);
- final int animLayer = win.mWinAnimator.mAnimLayer;
- if (win.mAttachedWindow == null && win.isVisibleLw() &&
- animLayer > mFocusedTaskLayer) {
- mFocusedTaskLayer = animLayer + LAYER_OFFSET_FOCUSED_TASK;
- }
- }
- }
- if (DEBUG_LAYERS) Slog.v(TAG, "Setting FocusedTaskFrame to layer=" + mFocusedTaskLayer);
- mFocusedTaskFrame.setLayer(mFocusedTaskLayer);
- }
-
- void setFocusedTaskFrame() {
- Task task = null;
- if (mFocusedApp != null) {
- task = mFocusedApp.mTask;
+ final Task task = mFocusedApp.mTask;
final DisplayContent displayContent = task.getDisplayContent();
if (displayContent != null) {
displayContent.setTouchExcludeRegion(task);
}
}
- mFocusedTaskFrame.setVisibility(task);
}
@Override
@@ -4129,15 +4106,7 @@
if (changed) {
mFocusedApp = newFocus;
mInputMonitor.setFocusedAppLw(newFocus);
- setFocusedTaskFrame();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedApp");
- SurfaceControl.openTransaction();
- try {
- setFocusedTaskLayer();
- } finally {
- SurfaceControl.closeTransaction();
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedApp");
- }
+ setFocusTaskRegion();
}
if (moveFocusNow && changed) {
@@ -10409,7 +10378,7 @@
if (updateInputWindowsNeeded) {
mInputMonitor.updateInputWindowsLw(false /*force*/);
}
- setFocusedTaskFrame();
+ setFocusTaskRegion();
// Check to see if we are now in a state where the screen should
// be enabled, because the window obscured flags have changed.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f7bf6e4..4061149 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -859,12 +859,9 @@
Task getTask() {
AppWindowToken wtoken = mAppToken == null ? mService.mFocusedApp : mAppToken;
if (wtoken == null) {
- Slog.e(TAG, "getTask: " + this + " null wtoken " + " Callers=" + Debug.getCallers(5));
return null;
}
final Task task = wtoken.mTask;
- if (task == null) Slog.e(TAG, "getStack: " + this + " couldn't find task for " + wtoken
- + " Callers=" + Debug.getCallers(5));
return task;
}
@@ -874,7 +871,6 @@
if (task.mStack != null) {
return task.mStack;
}
- Slog.e(TAG, "getStack: mStack null for task=" + task);
}
return mDisplayContent.getHomeStack();
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 04d6d98..1788e88 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -48,6 +48,7 @@
import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -225,6 +226,7 @@
mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
&& structureEnabled) {
try {
+ MetricsLogger.count(mContext, "assist_with_context", 1);
if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
mAssistReceiver, activityToken)) {
needDisclosure = true;
@@ -249,6 +251,7 @@
mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
&& screenshotEnabled) {
try {
+ MetricsLogger.count(mContext, "assist_with_screen", 1);
needDisclosure = true;
mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
} catch (RemoteException e) {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 49f738b..d91fa90 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1019,6 +1019,7 @@
&& !parcelableCall.getCannedSmsResponses().isEmpty()) {
mCannedTextResponses =
Collections.unmodifiableList(parcelableCall.getCannedSmsResponses());
+ cannedTextResponsesChanged = true;
}
boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() &&
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 706dcfb..7b277c5 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -253,12 +253,6 @@
//**********************************************************************************************
/**
- * TODO: Remove this -- retained so the build won't break. However, future work will remove it.
- * @hide
- */
- public static final String EXTRA_CALL_HISTORY_INFO = "android.telecom.EXTRA_CALL_HISTORY_INFO";
-
- /**
* Connection extra key used to store the last forwarded number associated with the current
* connection. Used to communicate to the user interface that the connection was forwarded via
* the specified number.
@@ -1594,7 +1588,7 @@
return mUnmodifiableConferenceables;
}
- /*
+ /**
* @hide
*/
public final void setConnectionService(ConnectionService connectionService) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e9c41a1..b5e4342 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -259,6 +259,14 @@
= "carrier_allow_turnoff_ims_bool";
/**
+ * Flag specifying whether IMS instant lettering is available for the carrier. {@code True} if
+ * instant lettering is available for the carrier, {@code false} otherwise.
+ * @hide
+ */
+ public static final String KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL =
+ "carrier_instant_lettering_available_bool";
+
+ /**
* If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0
* this is the value that should be used instead. A configuration value of
* RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default
@@ -393,6 +401,7 @@
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL, false);
sDefaults.putBoolean(KEY_DTMF_TYPE_ENABLED_BOOL, false);
sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a8066d8..88612e9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2060,6 +2060,8 @@
* <p>
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * OR
+ * {@link android.Manifest.permission#READ_SMS}
* <p>
* The default SMS app can also use this.
*/
@@ -2073,6 +2075,8 @@
* <p>
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * OR
+ * {@link android.Manifest.permission#READ_SMS}
* <p>
* The default SMS app can also use this.
*
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index e44969d..6177784 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -93,7 +93,7 @@
}
try {
- mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false, false);
+ mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0, false, false, null);
fail("IWindowManager.addAppToken did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
@@ -103,7 +103,7 @@
}
try {
- mWm.setAppTask(null, 0);
+ mWm.setAppTask(null, 0, null);
fail("IWindowManager.setAppGroupId did not throw SecurityException as"
+ " expected");
} catch (SecurityException e) {
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 2e515fb..6a61090 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -239,15 +239,12 @@
public int getInt(int index, int defValue) {
String s = getString(index);
try {
- if (s != null) {
- return convertValueToInt(s, defValue);
- }
+ return convertValueToInt(s, defValue);
} catch (NumberFormatException e) {
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer",
s, mNames[index]),
null);
- return defValue;
}
return defValue;
}
@@ -949,7 +946,7 @@
* "XXXXXXXX" > 80000000.
*/
private static int convertValueToInt(@Nullable String charSeq, int defValue) {
- if (null == charSeq)
+ if (null == charSeq || charSeq.isEmpty())
return defValue;
int sign = 1;
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 82012c1..62859ec 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Point;
+import android.graphics.Rect;
import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -72,11 +73,11 @@
// ---- unused implementation of IWindowManager ----
@Override
- public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
- boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10)
- throws RemoteException {
+ public Configuration addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4,
+ boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10,
+ Rect arg11) throws RemoteException {
// TODO Auto-generated method stub
-
+ return Configuration.EMPTY;
}
@Override
@@ -307,9 +308,9 @@
}
@Override
- public void setAppTask(IBinder arg0, int arg1) throws RemoteException {
+ public Configuration setAppTask(IBinder arg0, int arg1, Rect arg2) throws RemoteException {
// TODO Auto-generated method stub
-
+ return Configuration.EMPTY;
}
@Override
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
index c72c979..cbd0415 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
@@ -131,6 +131,7 @@
HardwareConfig hwConfig = getParams().getHardwareConfig();
Density density = hwConfig.getDensity();
boolean isRtl = Bridge.isLocaleRtl(getParams().getLocale());
+ setLayoutDirection(isRtl? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR);
NavigationBar navBar = null;
if (mBuilder.hasNavBar()) {