diff options
16 files changed, 609 insertions, 125 deletions
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java index 471fa3bd046e..214dc5d35a24 100644 --- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java +++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java @@ -51,7 +51,8 @@ public final class Dpm extends BaseCommand { out.println( "usage: dpm [subcommand] [options]\n" + "usage: dpm set-active-admin [ --user <USER_ID> ] <COMPONENT>\n" + - "usage: dpm set-device-owner <COMPONENT>\n" + + // STOPSHIP Finalize it + "usage: dpm set-device-owner [ --user <USER_ID> *EXPERIMENTAL* ] <COMPONENT>\n" + "usage: dpm set-profile-owner [ --user <USER_ID> ] <COMPONENT>\n" + "\n" + "dpm set-active-admin: Sets the given component as active admin" + @@ -106,22 +107,22 @@ public final class Dpm extends BaseCommand { } private void runSetDeviceOwner() throws RemoteException { - ComponentName component = parseComponentName(nextArgRequired()); - mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, UserHandle.USER_SYSTEM); + parseArgs(true); + mDevicePolicyManager.setActiveAdmin(mComponent, true /*refreshing*/, mUserId); - String packageName = component.getPackageName(); + String packageName = mComponent.getPackageName(); try { - if (!mDevicePolicyManager.setDeviceOwner(packageName, null /*ownerName*/)) { + if (!mDevicePolicyManager.setDeviceOwner(packageName, null /*ownerName*/, mUserId)) { throw new RuntimeException( "Can't set package " + packageName + " as device owner."); } } catch (Exception e) { // Need to remove the admin that we just added. - mDevicePolicyManager.removeActiveAdmin(component, UserHandle.USER_SYSTEM); + mDevicePolicyManager.removeActiveAdmin(mComponent, UserHandle.USER_SYSTEM); throw e; } System.out.println("Success: Device owner set to package " + packageName); - System.out.println("Active admin set to component " + component.toShortString()); + System.out.println("Active admin set to component " + mComponent.toShortString()); } private void runSetProfileOwner() throws RemoteException { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 7ffac0a6dc39..ac50699ca110 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2684,13 +2684,26 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the package name is null or invalid * @throws IllegalStateException If the preconditions mentioned are not met. */ - public boolean setDeviceOwner(String packageName) throws IllegalArgumentException, - IllegalStateException { + public boolean setDeviceOwner(String packageName) { return setDeviceOwner(packageName, null); } /** * @hide + */ + public boolean setDeviceOwner(String packageName, int userId) { + return setDeviceOwner(packageName, null, userId); + } + + /** + * @hide + */ + public boolean setDeviceOwner(String packageName, String ownerName) { + return setDeviceOwner(packageName, ownerName, UserHandle.USER_SYSTEM); + } + + /** + * @hide * Sets the given package as the device owner. The package must already be installed. There * must not already be a device owner. * Only apps with the MANAGE_PROFILE_AND_DEVICE_OWNERS permission and the shell uid can call @@ -2699,15 +2712,16 @@ public class DevicePolicyManager { * the caller is the shell uid, and there are no additional users and no accounts. * @param packageName the package name of the application to be registered as the device owner. * @param ownerName the human readable name of the institution that owns this device. + * @param userId ID of the user on which the device owner runs. * @return whether the package was successfully registered as the device owner. * @throws IllegalArgumentException if the package name is null or invalid * @throws IllegalStateException If the preconditions mentioned are not met. */ - public boolean setDeviceOwner(String packageName, String ownerName) + public boolean setDeviceOwner(String packageName, String ownerName, int userId) throws IllegalArgumentException, IllegalStateException { if (mService != null) { try { - return mService.setDeviceOwner(packageName, ownerName); + return mService.setDeviceOwner(packageName, ownerName, userId); } catch (RemoteException re) { Log.w(TAG, "Failed to set device owner"); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 376a3d82f7f1..55a21c61f53a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -113,7 +113,7 @@ interface IDevicePolicyManager { void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); - boolean setDeviceOwner(String packageName, String ownerName); + boolean setDeviceOwner(String packageName, String ownerName, int userId); boolean isDeviceOwner(String packageName); String getDeviceOwner(); String getDeviceOwnerName(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index edf829d178c9..93921dd51490 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -62,6 +62,7 @@ import android.os.SystemProperties; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.support.design.widget.Snackbar; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; @@ -132,7 +133,7 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_QUERY = "query"; private static final String EXTRA_IGNORE_STATE = "ignoreState"; - private final Model mModel = new Model(); + private Model mModel; private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -304,7 +305,10 @@ public class DirectoryFragment extends Fragment { ? MultiSelectManager.MODE_MULTIPLE : MultiSelectManager.MODE_SINGLE); selMgr.addCallback(new SelectionModeListener()); + + mModel = new Model(context, selMgr); mModel.setSelectionManager(selMgr); + mModel.addUpdateListener(mAdapter); mType = getArguments().getInt(EXTRA_TYPE); mStateKey = buildStateKey(root, doc); @@ -374,9 +378,7 @@ public class DirectoryFragment extends Fragment { if (!isAdded()) return; - // TODO: make the adapter listen to the model mModel.update(result); - mAdapter.update(); // Push latest state up to UI // TODO: if mode change was racing with us, don't overwrite it @@ -407,9 +409,7 @@ public class DirectoryFragment extends Fragment { @Override public void onLoaderReset(Loader<DirectoryResult> loader) { - // TODO: make the adapter listen to the model. mModel.update(null); - mAdapter.update(); } }; @@ -827,9 +827,9 @@ public class DirectoryFragment extends Fragment { if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { mModel.undoDeletion(); } else { - mModel.finalizeDeletion(); + // TODO: Use a listener rather than pushing the view. + mModel.finalizeDeletion(DirectoryFragment.this.getView()); } - ; } }) .show(); @@ -953,7 +953,8 @@ public class DirectoryFragment extends Fragment { } } - private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder> { + private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder> + implements Model.UpdateListener { private final Context mContext; private final LayoutInflater mInflater; @@ -965,19 +966,19 @@ public class DirectoryFragment extends Fragment { mInflater = LayoutInflater.from(context); } - public void update() { + public void onModelUpdate(Model model) { mFooters.clear(); - if (mModel.info != null) { - mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, mModel.info)); + if (model.info != null) { + mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, model.info)); } - if (mModel.error != null) { - mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, mModel.error)); + if (model.error != null) { + mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, model.error)); } - if (mModel.isLoading()) { + if (model.isLoading()) { mFooters.add(new LoadingFooter()); } - if (mModel.isEmpty()) { + if (model.isEmpty()) { mEmptyView.setVisibility(View.VISIBLE); } else { mEmptyView.setVisibility(View.GONE); @@ -986,6 +987,12 @@ public class DirectoryFragment extends Fragment { notifyDataSetChanged(); } + public void onModelUpdateFailed(Exception e) { + String error = getString(R.string.query_error); + mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error)); + notifyDataSetChanged(); + } + @Override public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) { final State state = getDisplayState(DirectoryFragment.this); @@ -1736,14 +1743,22 @@ public class DirectoryFragment extends Fragment { /** * The data model for the current loaded directory. */ - private final class Model implements DocumentContext { + @VisibleForTesting + public static final class Model implements DocumentContext { private MultiSelectManager mSelectionManager; + private Context mContext; private int mCursorCount; private boolean mIsLoading; + private SparseBooleanArray mMarkedForDeletion = new SparseBooleanArray(); + private UpdateListener mUpdateListener; @Nullable private Cursor mCursor; @Nullable private String info; @Nullable private String error; - private SparseBooleanArray mMarkedForDeletion = new SparseBooleanArray(); + + Model(Context context, MultiSelectManager selectionManager) { + mContext = context; + mSelectionManager = selectionManager; + } /** * Sets the selection manager used by the model. @@ -1794,12 +1809,13 @@ public class DirectoryFragment extends Fragment { info = null; error = null; mIsLoading = false; + if (mUpdateListener != null) mUpdateListener.onModelUpdate(this); return; } if (result.exception != null) { Log.e(TAG, "Error while loading directory contents", result.exception); - error = getString(R.string.query_error); + if (mUpdateListener != null) mUpdateListener.onModelUpdateFailed(result.exception); return; } @@ -1812,13 +1828,15 @@ public class DirectoryFragment extends Fragment { error = extras.getString(DocumentsContract.EXTRA_ERROR); mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false); } + + if (mUpdateListener != null) mUpdateListener.onModelUpdate(this); } - private int getItemCount() { + int getItemCount() { return mCursorCount - mMarkedForDeletion.size(); } - private Cursor getItem(int position) { + Cursor getItem(int position) { // Items marked for deletion are masked out of the UI. To do this, for every marked // item whose position is less than the requested item position, advance the requested // position by 1. @@ -1859,7 +1877,7 @@ public class DirectoryFragment extends Fragment { return getDocuments(sel); } - private List<DocumentInfo> getDocuments(Selection items) { + List<DocumentInfo> getDocuments(Selection items) { final int size = (items != null) ? items.size() : 0; final List<DocumentInfo> docs = new ArrayList<>(size); @@ -1880,7 +1898,7 @@ public class DirectoryFragment extends Fragment { return mCursor; } - private List<DocumentInfo> getDocumentsMarkedForDeletion() { + List<DocumentInfo> getDocumentsMarkedForDeletion() { final int size = mMarkedForDeletion.size(); List<DocumentInfo> docs = new ArrayList<>(size); @@ -1901,7 +1919,7 @@ public class DirectoryFragment extends Fragment { * * @param selected A selection representing the files to delete. */ - public void markForDeletion(Selection selected) { + void markForDeletion(Selection selected) { // Only one deletion operation at a time. checkState(mMarkedForDeletion.size() == 0); // There should never be more to delete than what exists. @@ -1912,7 +1930,7 @@ public class DirectoryFragment extends Fragment { int position = selected.get(i); if (DEBUG) Log.d(TAG, "Marked position " + position + " for deletion"); mMarkedForDeletion.append(position, true); - mAdapter.notifyItemRemoved(position); + if (mUpdateListener != null) mUpdateListener.notifyItemRemoved(position); } } @@ -1920,14 +1938,14 @@ public class DirectoryFragment extends Fragment { * Cancels an ongoing deletion operation. All files currently marked for deletion will be * unmarked, and restored in the UI. See {@link #markForDeletion(Selection)}. */ - public void undoDeletion() { + void undoDeletion() { // Iterate over deleted items, temporarily marking them false in the deletion list, and // re-adding them to the UI. final int size = mMarkedForDeletion.size(); for (int i = 0; i < size; ++i) { final int position = mMarkedForDeletion.keyAt(i); mMarkedForDeletion.put(position, false); - mAdapter.notifyItemInserted(position); + if (mUpdateListener != null) mUpdateListener.notifyItemInserted(position); } // Then, clear the deletion list. @@ -1937,11 +1955,26 @@ public class DirectoryFragment extends Fragment { /** * Finalizes an ongoing deletion operation. All files currently marked for deletion will be * deleted. See {@link #markForDeletion(Selection)}. + * + * @param view The view which will be used to interact with the user (e.g. surfacing + * snackbars) for errors, info, etc. */ - public void finalizeDeletion() { - final Context context = getActivity(); - final ContentResolver resolver = context.getContentResolver(); - new DeleteFilesTask(resolver).execute(); + void finalizeDeletion(final View view) { + final ContentResolver resolver = mContext.getContentResolver(); + DeleteFilesTask task = new DeleteFilesTask( + resolver, + new Runnable() { + @Override + public void run() { + Snackbar.make( + view, + R.string.toast_failed_delete, + Snackbar.LENGTH_LONG) + .show(); + + } + }); + task.execute(); } /** @@ -1950,9 +1983,16 @@ public class DirectoryFragment extends Fragment { */ private class DeleteFilesTask extends AsyncTask<Void, Void, List<DocumentInfo>> { private ContentResolver mResolver; - - public DeleteFilesTask(ContentResolver resolver) { + private Runnable mErrorCallback; + + /** + * @param resolver A ContentResolver for performing the actual file deletions. + * @param errorCallback A Runnable that is executed in the event that one or more errors + * occured while copying files. Execution will occur on the UI thread. + */ + public DeleteFilesTask(ContentResolver resolver, Runnable errorCallback) { mResolver = resolver; + mErrorCallback = errorCallback; } @Override @@ -1985,10 +2025,8 @@ public class DirectoryFragment extends Fragment { } if (hadTrouble) { - // TODO show which files failed? - Snackbar.make(DirectoryFragment.this.getView(), - R.string.toast_failed_delete, - Snackbar.LENGTH_LONG).show(); + // TODO show which files failed? b/23720103 + mErrorCallback.run(); if (DEBUG) Log.d(TAG, "Deletion task completed. Some deletions failed."); } else { if (DEBUG) Log.d(TAG, "Deletion task completed successfully."); @@ -1996,5 +2034,32 @@ public class DirectoryFragment extends Fragment { mMarkedForDeletion.clear(); } } + + void addUpdateListener(UpdateListener listener) { + checkState(mUpdateListener == null); + mUpdateListener = listener; + } + + interface UpdateListener { + /** + * Called when a successful update has occurred. + */ + void onModelUpdate(Model model); + + /** + * Called when an update has been attempted but failed. + */ + void onModelUpdateFailed(Exception e); + + /** + * Called when an item has been removed from the model. + */ + void notifyItemRemoved(int position); + + /** + * Called when an item has been added to the model. + */ + void notifyItemInserted(int position); + } } } diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java new file mode 100644 index 000000000000..4331b03291e8 --- /dev/null +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java @@ -0,0 +1,157 @@ +/* + * 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.documentsui; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.provider.DocumentsContract.Document; +import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import android.test.mock.MockContentResolver; + +import com.android.documentsui.DirectoryFragment.Model; +import com.android.documentsui.MultiSelectManager.Selection; +import com.android.documentsui.model.DocumentInfo; + +import java.util.List; + + +public class DirectoryFragmentModelTest extends AndroidTestCase { + + private static final int ITEM_COUNT = 5; + private static final String[] COLUMNS = new String[]{ + Document.COLUMN_DOCUMENT_ID + }; + private static Cursor cursor; + + private Context mContext; + private Model model; + + public void setUp() { + setupTestContext(); + + MatrixCursor c = new MatrixCursor(COLUMNS); + for (int i = 0; i < ITEM_COUNT; ++i) { + MatrixCursor.RowBuilder row = c.newRow(); + row.add(COLUMNS[0], i); + } + cursor = c; + + DirectoryResult r = new DirectoryResult(); + r.cursor = cursor; + + model = new Model(mContext, null); + model.update(r); + } + + // Tests that the item count is correct. + public void testItemCount() { + assertEquals(ITEM_COUNT, model.getItemCount()); + } + + // Tests that the item count is correct after a deletion. + public void testItemCount_WithDeletion() { + // Simulate deleting 2 files. + delete(2, 4); + + assertEquals(ITEM_COUNT - 2, model.getItemCount()); + + // Finalize the deletion + model.finalizeDeletion(null); + assertEquals(ITEM_COUNT - 2, model.getItemCount()); + } + + // Tests that the item count is correct after a deletion is undone. + public void testItemCount_WithUndoneDeletion() { + // Simulate deleting 2 files. + delete(0, 3); + + // Undo the deletion + model.undoDeletion(); + assertEquals(ITEM_COUNT, model.getItemCount()); + + } + + // Tests that the right things are marked for deletion. + public void testMarkForDeletion() { + delete(1, 3); + + List<DocumentInfo> docs = model.getDocumentsMarkedForDeletion(); + assertEquals(2, docs.size()); + assertEquals("1", docs.get(0).documentId); + assertEquals("3", docs.get(1).documentId); + } + + // Tests the base case for Model.getItem. + public void testGetItem() { + for (int i = 0; i < ITEM_COUNT; ++i) { + Cursor c = model.getItem(i); + assertEquals(i, c.getPosition()); + } + } + + // Tests that Model.getItem returns the right items after a deletion. + public void testGetItem_WithDeletion() { + // Simulate deleting 2 files. + delete(2, 3); + + List<DocumentInfo> docs = getDocumentInfo(0, 1, 2); + assertEquals("0", docs.get(0).documentId); + assertEquals("1", docs.get(1).documentId); + assertEquals("4", docs.get(2).documentId); + } + + // Tests that Model.getItem returns the right items after a deletion is undone. + public void testGetItem_WithCancelledDeletion() { + delete(0, 1); + model.undoDeletion(); + + // Test that all documents are accounted for, in the right position. + for (int i = 0; i < ITEM_COUNT; ++i) { + assertEquals(Integer.toString(i), getDocumentInfo(i).get(0).documentId); + } + } + + private void setupTestContext() { + final MockContentResolver resolver = new MockContentResolver(); + mContext = new ContextWrapper(getContext()) { + @Override + public ContentResolver getContentResolver() { + return resolver; + } + }; + } + + private void delete(int... items) { + Selection sel = new Selection(); + for (int item: items) { + sel.add(item); + } + model.markForDeletion(sel); + } + + private List<DocumentInfo> getDocumentInfo(int... items) { + Selection sel = new Selection(); + for (int item: items) { + sel.add(item); + } + return model.getDocuments(sel); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index 6475cbf158cb..b2f527e3bbb4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -104,6 +104,7 @@ public class VolumeDialog { private final SpTexts mSpTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; + private final AudioManager mAudioManager; private final int mExpandButtonAnimationDuration; private final ZenFooter mZenFooter; private final LayoutTransition mLayoutTransition; @@ -136,6 +137,7 @@ public class VolumeDialog { mCallback = callback; mSpTexts = new SpTexts(mContext); mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mDialog = new CustomDialog(mContext); @@ -649,7 +651,8 @@ public class VolumeDialog { private void updateFooterH() { if (D.BUG) Log.d(TAG, "updateFooterH"); final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE; - final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF; + final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF + && mAudioManager.isStreamAffectedByRingerMode(mActiveStream); if (wasVisible != visible && !visible) { prepareForCollapse(); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b7c55d43ffbf..dd9b6f13869d 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -3092,8 +3092,11 @@ public final class ActivityStackSupervisor implements DisplayListener { // to size change. The activity will first remove the old window and then add a // new one. This call will tell window manager about this, so it can preserve // the old window until the new one is drawn. This prevents having a gap between - // the removal and addition, in which no window is visible. - mWindowManager.setReplacingWindow(r.appToken); + // the removal and addition, in which no window is visible. If we also changed + // the stack to the fullscreen stack, i.e. maximized the window, we will animate + // the transition. + mWindowManager.setReplacingWindow(r.appToken, + stackId == FULLSCREEN_WORKSPACE_STACK_ID /* animate */); } } } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index de7c07ebaee9..c76e64c4ff44 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -122,6 +122,8 @@ public class AppTransition implements Dump { public static final int TRANSIT_TASK_OPEN_BEHIND = 16; /** A window in a task is being animated in-place. */ public static final int TRANSIT_TASK_IN_PLACE = 17; + /** An activity is being relaunched (e.g. due to configuration change). */ + public static final int TRANSIT_ACTIVITY_RELAUNCH = 18; /** Fraction of animation at which the recents thumbnail stays completely transparent */ private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.5f; @@ -1004,6 +1006,22 @@ public class AppTransition implements Dump { return prepareThumbnailAnimation(a, appWidth, appHeight, transit); } + private Animation createRelaunchAnimation(int appWidth, int appHeight) { + getDefaultNextAppTransitionStartRect(mTmpFromClipRect); + final int left = mTmpFromClipRect.left; + final int top = mTmpFromClipRect.top; + mTmpFromClipRect.offset(-left, -top); + mTmpToClipRect.set(0, 0, appWidth, appHeight); + AnimationSet set = new AnimationSet(true); + ClipRectAnimation clip = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect); + TranslateAnimation translate = new TranslateAnimation(left, 0, top, 0); + clip.setInterpolator(mDecelerateInterpolator); + set.addAnimation(clip); + set.addAnimation(translate); + set.setDuration(DEFAULT_APP_TRANSITION_DURATION); + return set; + } + /** * @return true if and only if the first frame of the transition can be skipped, i.e. the first * frame of the transition doesn't change the visuals on screen, so we can start @@ -1040,6 +1058,8 @@ public class AppTransition implements Dump { "applyAnimation voice:" + " anim=" + a + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); + } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) { + a = createRelaunchAnimation(appWidth, appHeight); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) { a = loadAnimationRes(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); @@ -1174,7 +1194,7 @@ public class AppTransition implements Dump { } void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, - IRemoteCallback startedCallback) { + IRemoteCallback startedCallback) { if (isTransitionSet()) { mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM; mNextAppTransitionPackage = packageName; @@ -1249,12 +1269,20 @@ public class AppTransition implements Dump { mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP : NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; mNextAppTransitionPackage = null; + mDefaultNextAppTransitionAnimationSpec = null; mNextAppTransitionAnimationsSpecs.clear(); mNextAppTransitionScaleUp = scaleUp; for (int i = 0; i < specs.length; i++) { AppTransitionAnimationSpec spec = specs[i]; if (spec != null) { mNextAppTransitionAnimationsSpecs.put(spec.taskId, spec); + if (i == 0) { + // In full screen mode, the transition code depends on the default spec to + // be set. + Rect rect = spec.rect; + putDefaultNextAppTransitionCoordinates(rect.left, rect.top, rect.width(), + rect.height()); + } } } postAnimationCallback(); @@ -1326,6 +1354,9 @@ public class AppTransition implements Dump { case TRANSIT_TASK_OPEN_BEHIND: { return "TRANSIT_TASK_OPEN_BEHIND"; } + case TRANSIT_ACTIVITY_RELAUNCH: { + return "TRANSIT_ACTIVITY_RELAUNCH"; + } default: { return "<UNKNOWN>"; } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 6ee2c3952f2d..4f62909c061a 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -116,6 +116,10 @@ class AppWindowToken extends WindowToken { // to differentiate between simple removal of a window and replacement. In the latter case it // will preserve the old window until the new one is drawn. boolean mReplacingWindow; + // If true, the replaced window was already requested to be removed. + boolean mReplacingRemoveRequested; + // Whether the replacement of the window should trigger app transition animation. + boolean mAnimateReplacingWindow; AppWindowToken(WindowManagerService _service, IApplicationToken _token, boolean _voiceInteraction) { @@ -230,16 +234,24 @@ class AppWindowToken extends WindowToken { } WindowState findMainWindow() { + WindowState candidate = null; int j = windows.size(); while (j > 0) { j--; WindowState win = windows.get(j); if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { - return win; + // In cases where there are multiple windows, we prefer the non-exiting window. This + // happens for example when when replacing windows during an activity relaunch. When + // constructing the animation, we want the new window, not the exiting one. + if (win.mExiting) { + candidate = win; + } else { + return win; + } } } - return null; + return candidate; } boolean isVisible() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index ebabc5201583..f3e7d1d58313 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -125,7 +125,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.Watchdog; -import com.android.server.am.BatteryStatsService; import com.android.server.input.InputManagerService; import com.android.server.policy.PhoneWindowManager; import com.android.server.power.ShutdownThread; @@ -290,6 +289,10 @@ public class WindowManagerService extends IWindowManager.Stub private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; + // Used to indicate that if there is already a transition set, it should be preserved when + // trying to apply a new one. + private static final boolean ALWAYS_KEEP_CURRENT = true; + final private KeyguardDisableHandler mKeyguardDisableHandler; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -1740,6 +1743,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); + AppWindowToken atoken = null; if (token == null) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG, "Attempted to add application window with unknown token " @@ -1774,7 +1778,7 @@ public class WindowManagerService extends IWindowManager.Stub token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { - AppWindowToken atoken = token.appWindowToken; + atoken = token.appWindowToken; if (atoken == null) { Slog.w(TAG, "Attempted to add window with non-application token " + token + ". Aborting."); @@ -1920,6 +1924,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowStateAnimator winAnimator = win.mWinAnimator; winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; + prepareWindowReplacementTransition(atoken); if (displayContent.isDefaultDisplay) { mPolicy.getInsetHintLw(win.mAttrs, mRotation, outContentInsets, outStableInsets, @@ -1977,6 +1982,33 @@ public class WindowManagerService extends IWindowManager.Stub return res; } + private void prepareWindowReplacementTransition(AppWindowToken atoken) { + if (atoken == null || !atoken.mReplacingWindow || !atoken.mAnimateReplacingWindow) { + return; + } + atoken.allDrawn = false; + WindowState replacedWindow = null; + for (int i = atoken.windows.size() - 1; i >= 0 && replacedWindow == null; i--) { + WindowState candidate = atoken.windows.get(i); + if (candidate.mExiting) { + replacedWindow = candidate; + } + } + if (replacedWindow == null) { + // We expect to already receive a request to remove the old window. If it did not + // happen, let's just simply add a window. + return; + } + Rect frame = replacedWindow.mFrame; + // We treat this as if this activity was opening, so we can trigger the app transition + // animation and piggy-back on existing transition animation infrastructure. + mOpeningApps.add(atoken); + prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_RELAUNCH, ALWAYS_KEEP_CURRENT); + mAppTransition.overridePendingAppTransitionClipReveal(frame.left, frame.top, + frame.width(), frame.height()); + executeAppTransition(); + } + /** * Returns whether screen capture is disabled for all windows of a specific user. */ @@ -2072,6 +2104,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Preserving " + win + " until the new one is " + "added"); win.mExiting = true; + appToken.mReplacingRemoveRequested = true; return; } // If we are not currently running the exit animation, we @@ -2730,6 +2763,7 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized (mWindowMap) { WindowState win = windowForClientLocked(session, client, false); + if (DEBUG_ADD_REMOVE) Slog.d(TAG, "finishDrawingWindow: " + win); if (win != null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { getDefaultDisplayContentLocked().pendingLayoutChanges |= @@ -3426,6 +3460,12 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * @param transit What kind of transition is happening. Use one of the constants + * AppTransition.TRANSIT_*. + * @param alwaysKeepCurrent If true and a transition is already set, new transition will NOT + * be set. + */ @Override public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, @@ -3822,10 +3862,13 @@ public class WindowManagerService extends IWindowManager.Stub } wtoken.willBeHidden = false; - // Allow for state changes and animation to be applied if token is transitioning - // visibility state or the token was marked as hidden and is exiting before we had a chance - // to play the transition animation. - if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting)) { + // Allow for state changes and animation to be applied if: + // * token is transitioning visibility state + // * or the token was marked as hidden and is exiting before we had a chance to play the + // transition animation + // * or this is an opening app and windows are being replaced. + if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) || + (visible && wtoken.mReplacingWindow)) { boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden @@ -9723,7 +9766,7 @@ public class WindowManagerService extends IWindowManager.Stub return mWindowMap; } - public void setReplacingWindow(IBinder token) { + public void setReplacingWindow(IBinder token, boolean animate) { synchronized (mWindowMap) { AppWindowToken appWindowToken = findAppWindowToken(token); if (appWindowToken == null) { @@ -9731,6 +9774,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } appWindowToken.mReplacingWindow = true; + appWindowToken.mAnimateReplacingWindow = animate; } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 49d260e46f19..d756d3912170 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -545,6 +545,14 @@ final class WindowState implements WindowManagerPolicy.WindowState { @Override public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf, Rect osf) { + if (mAppToken != null && mAppToken.mReplacingWindow + && (mExiting || !mAppToken.mReplacingRemoveRequested)) { + // This window is being replaced and either already got information that it's being + // removed or we are still waiting for some information. Because of this we don't + // want to apply any more changes to it, so it remains in this state until new window + // appears. + return; + } mHaveFrame = true; final Task task = mAppToken != null ? getTask() : null; @@ -1274,6 +1282,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { AppWindowToken token = mAppToken; if (token != null && token.mReplacingWindow) { token.mReplacingWindow = false; + token.mAnimateReplacingWindow = false; + token.mReplacingRemoveRequested = false; for (int i = token.allAppWindows.size() - 1; i >= 0; i--) { WindowState win = token.allAppWindows.get(i); if (win.mExiting) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index efad8bf6bef9..066dc9554ebc 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1501,6 +1501,7 @@ class WindowStateAnimator { w.mLastHScale = w.mHScale; w.mLastVScale = w.mVScale; if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "control=" + mSurfaceControl + "alpha=" + mShownAlpha + " layer=" + mAnimLayer + " matrix=[" + mDsDx + "*" + w.mHScale + "," + mDtDx + "*" + w.mVScale diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index ce07a9d859be..8582ca28bd4a 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -47,7 +47,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.provider.Settings; -import android.util.Log; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; @@ -305,9 +304,9 @@ class WindowSurfacePlacer { if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats( "On entry to LockedInner", displayContent.pendingLayoutChanges); - if ((displayContent.pendingLayoutChanges & - FINISH_LAYOUT_REDO_WALLPAPER) != 0 && - mWallpaperControllerLocked.adjustWallpaperWindows()) { + if ((displayContent.pendingLayoutChanges + & FINISH_LAYOUT_REDO_WALLPAPER) != 0 + && mWallpaperControllerLocked.adjustWallpaperWindows()) { mService.assignLayersLocked(windows); displayContent.layoutNeeded = true; } @@ -430,8 +429,7 @@ class WindowSurfacePlacer { // Moved from updateWindowsAndWallpaperLocked(). if (w.mHasSurface) { // Take care of the window being ready to display. - final boolean committed = - winAnimator.commitFinishDrawingLocked(); + final boolean committed = winAnimator.commitFinishDrawingLocked(); if (isDefaultDisplay && committed) { if (w.mAttrs.type == TYPE_DREAM) { // HACK: When a dream is shown, it may at that @@ -447,9 +445,8 @@ class WindowSurfacePlacer { } } if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) { - if (DEBUG_WALLPAPER_LIGHT) - Slog.v(TAG, - "First draw done in potential wallpaper target " + w); + if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, + "First draw done in potential wallpaper target " + w); mWallpaperMayChange = true; displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; @@ -544,7 +541,6 @@ class WindowSurfacePlacer { // Give the display manager a chance to adjust properties // like display rotation if it needs to. mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager(); - } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { @@ -1461,8 +1457,6 @@ class WindowSurfacePlacer { // open/close animation (only on the way down) anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect, thumbnailHeader, taskId); - Log.d(TAG, "assigning thumbnail force above layer: " - + openingLayer + " " + closingLayer); openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer); openingAppAnimator.deferThumbnailDestruction = !mService.mAppTransition.isNextThumbnailTransitionScaleUp(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 020230931097..6d7343dde26e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -303,9 +303,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void onBootPhase(int phase) { - if (phase == PHASE_LOCK_SETTINGS_READY) { - mService.systemReady(); - } + mService.systemReady(phase); } } @@ -1776,10 +1774,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void systemReady() { + public void systemReady(int phase) { if (!mHasFeature) { return; } + switch (phase) { + case SystemService.PHASE_LOCK_SETTINGS_READY: + onLockSettingsReady(); + break; + case SystemService.PHASE_BOOT_COMPLETED: + ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this. + break; + } + } + + private void onLockSettingsReady() { getUserData(UserHandle.USER_OWNER); loadDeviceOwner(); cleanUpOldUsers(); @@ -1798,6 +1807,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void ensureDeviceOwnerUserStarted() { + if (mOwners.hasDeviceOwner()) { + final IActivityManager am = ActivityManagerNative.getDefault(); + final int userId = mOwners.getDeviceOwnerUserId(); + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Starting non-system DO user: " + userId); + } + if (userId != UserHandle.USER_SYSTEM) { + try { + am.startUserInBackground(userId); + + // STOPSHIP Prevent the DO user from being killed. + + } catch (RemoteException e) { + Slog.w(LOG_TAG, "Exception starting user", e); + } + } + } + } + private void cleanUpOldUsers() { // This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled // before reboot @@ -4110,17 +4139,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean setDeviceOwner(String packageName, String ownerName) { + public boolean setDeviceOwner(String packageName, String ownerName, int userId) { if (!mHasFeature) { return false; } if (packageName == null - || !Owners.isInstalled(packageName, mContext.getPackageManager())) { + || !Owners.isInstalledForUser(packageName, userId)) { throw new IllegalArgumentException("Invalid package name " + packageName + " for device owner"); } synchronized (this) { - enforceCanSetDeviceOwner(); + enforceCanSetDeviceOwner(userId); // Shutting down backup manager service permanently. long ident = Binder.clearCallingIdentity(); @@ -4134,14 +4163,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Binder.restoreCallingIdentity(ident); } - mOwners.setDeviceOwner(packageName, ownerName); + mOwners.setDeviceOwner(packageName, ownerName, userId); mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED); ident = Binder.clearCallingIdentity(); try { - mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); + // TODO Send to system too? + mContext.sendBroadcastAsUser(intent, new UserHandle(userId)); } finally { Binder.restoreCallingIdentity(ident); } @@ -4242,10 +4272,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return false; } - if (initializer == null || !Owners.isInstalled( - initializer.getPackageName(), mContext.getPackageManager())) { + if (initializer == null || + !mOwners.hasDeviceOwner() || + !Owners.isInstalledForUser(initializer.getPackageName(), + mOwners.getDeviceOwnerUserId())) { throw new IllegalArgumentException("Invalid component name " + initializer - + " for device initializer"); + + " for device initializer or no device owner set"); } boolean isInitializerSystemApp; try { @@ -4268,7 +4300,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.setDeviceInitializer(initializer); - addDeviceInitializerToLockTaskPackagesLocked(UserHandle.USER_OWNER); + addDeviceInitializerToLockTaskPackagesLocked(mOwners.getDeviceOwnerUserId()); mOwners.writeDeviceOwner(); return true; } @@ -4278,7 +4310,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who == null) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_DEVICE_ADMINS, null); - if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { + if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) { throw new IllegalStateException( "Trying to set device initializer but device is already provisioned."); } @@ -4648,31 +4680,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The device owner can only be set before the setup phase of the primary user has completed, * except for adb if no accounts or additional users are present on the device. */ - private void enforceCanSetDeviceOwner() { - if (mOwners != null && mOwners.hasDeviceOwner()) { + private void enforceCanSetDeviceOwner(int userId) { + if (mOwners.hasDeviceOwner()) { throw new IllegalStateException("Trying to set the device owner, but device owner " + "is already set."); } + // STOPSHIP Make sure the DO user is running int callingUid = Binder.getCallingUid(); if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { if (!hasUserSetupCompleted(UserHandle.USER_OWNER)) { return; } - if (mUserManager.getUserCount() > 1) { - throw new IllegalStateException("Not allowed to set the device owner because there " - + "are already several users on the device"); - } - if (AccountManager.get(mContext).getAccounts().length > 0) { - throw new IllegalStateException("Not allowed to set the device owner because there " - + "are already some accounts on the device"); + // STOPSHIP Do proper check in split user mode + if (!UserManager.isSplitSystemUser()) { + if (mUserManager.getUserCount() > 1) { + throw new IllegalStateException( + "Not allowed to set the device owner because there " + + "are already several users on the device"); + } + if (AccountManager.get(mContext).getAccounts().length > 0) { + throw new IllegalStateException( + "Not allowed to set the device owner because there " + + "are already some accounts on the device"); + } } return; + } else { + // STOPSHIP check the caller UID with userId + } + if (mUserManager.isUserRunning(new UserHandle(userId))) { + throw new IllegalStateException("User not running: " + userId); } + mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null); - if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { - throw new IllegalStateException("Cannot set the device owner if the device is " - + "already set-up"); + // STOPSHIP Do proper check in split user mode + if (!UserManager.isSplitSystemUser()) { + if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { + throw new IllegalStateException("Cannot set the device owner if the device is " + + "already set-up"); + } } } @@ -4689,12 +4736,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void enforceSystemProcess(String message) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException(message); - } - } - private void enforceNotManagedProfile(int userHandle, String message) { if(isManagedProfile(userHandle)) { throw new SecurityException("You can not " + message + " for a managed profile. "); @@ -6318,7 +6359,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mContext.sendBroadcastAsUser( new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED), - UserHandle.OWNER); + UserHandle.SYSTEM); } @Override @@ -6355,7 +6396,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "Only the system update service can broadcast update information"); if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { - Slog.w(LOG_TAG, "Only the system update service in the primary user" + + Slog.w(LOG_TAG, "Only the system update service in the primary user " + "can broadcast update information."); return; } @@ -6368,6 +6409,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (deviceOwnerPackage == null) { return; } + final UserHandle deviceOwnerUser = new UserHandle(mOwners.getDeviceOwnerUserId()); ActivityInfo[] receivers = null; try { @@ -6383,7 +6425,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (permission.BIND_DEVICE_ADMIN.equals(receivers[i].permission)) { intent.setComponent(new ComponentName(deviceOwnerPackage, receivers[i].name)); - mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); + mContext.sendBroadcastAsUser(intent, deviceOwnerUser); } } } finally { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 165671612a44..87cf28f861d8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.os.Environment; import android.os.RemoteException; +import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; import android.util.AtomicFile; @@ -71,6 +72,8 @@ class Owners { private static final String TAG_DEVICE_OWNER = "device-owner"; private static final String TAG_DEVICE_INITIALIZER = "device-initializer"; private static final String TAG_PROFILE_OWNER = "profile-owner"; + // Holds "context" for device-owner, this must not be show up before device-owner. + private static final String TAG_DEVICE_OWNER_CONTEXT = "device-owner-context"; private static final String ATTR_NAME = "name"; private static final String ATTR_PACKAGE = "package"; @@ -85,6 +88,8 @@ class Owners { // Internal state for the device owner package. private OwnerInfo mDeviceOwner; + private int mDeviceOwnerUserId = UserHandle.USER_NULL; + // Internal state for the device initializer package. private OwnerInfo mDeviceInitializer; @@ -140,16 +145,26 @@ class Owners { return mDeviceOwner != null ? mDeviceOwner.packageName : null; } + int getDeviceOwnerUserId() { + return mDeviceOwnerUserId; + } + String getDeviceOwnerName() { return mDeviceOwner != null ? mDeviceOwner.name : null; } - void setDeviceOwner(String packageName, String ownerName) { + void setDeviceOwner(String packageName, String ownerName, int userId) { + if (userId < 0) { + Slog.e(TAG, "Invalid user id for device owner user: " + userId); + return; + } mDeviceOwner = new OwnerInfo(ownerName, packageName); + mDeviceOwnerUserId = userId; } void clearDeviceOwner() { mDeviceOwner = null; + mDeviceOwnerUserId = UserHandle.USER_NULL; } ComponentName getDeviceInitializerComponent() { @@ -215,20 +230,6 @@ class Owners { return mDeviceOwner != null; } - static boolean isInstalled(String packageName, PackageManager pm) { - try { - PackageInfo pi; - if ((pi = pm.getPackageInfo(packageName, 0)) != null) { - if ((pi.applicationInfo.flags) != 0) { - return true; - } - } - } catch (NameNotFoundException nnfe) { - Slog.w(TAG, "Device Owner package " + packageName + " not installed."); - } - return false; - } - static boolean isInstalledForUser(String packageName, int userHandle) { try { PackageInfo pi = (AppGlobals.getPackageManager()) @@ -263,6 +264,7 @@ class Owners { String name = parser.getAttributeValue(null, ATTR_NAME); String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); mDeviceOwner = new OwnerInfo(name, packageName); + mDeviceOwnerUserId = UserHandle.USER_SYSTEM; } else if (tag.equals(TAG_DEVICE_INITIALIZER)) { String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); String initializerComponentStr = @@ -464,7 +466,11 @@ class Owners { void writeInner(XmlSerializer out) throws IOException { if (mDeviceOwner != null) { mDeviceOwner.writeToXml(out, TAG_DEVICE_OWNER); + out.startTag(null, TAG_DEVICE_OWNER_CONTEXT); + out.attribute(null, ATTR_USERID, String.valueOf(mDeviceOwnerUserId)); + out.endTag(null, TAG_DEVICE_OWNER_CONTEXT); } + if (mDeviceInitializer != null) { mDeviceInitializer.writeToXml(out, TAG_DEVICE_INITIALIZER); } @@ -483,7 +489,18 @@ class Owners { switch (tag) { case TAG_DEVICE_OWNER: mDeviceOwner = OwnerInfo.readFromXml(parser); + mDeviceOwnerUserId = UserHandle.USER_SYSTEM; // Set default + break; + case TAG_DEVICE_OWNER_CONTEXT: { + final String userIdString = + parser.getAttributeValue(null, ATTR_USERID); + try { + mDeviceOwnerUserId = Integer.parseInt(userIdString); + } catch (NumberFormatException e) { + Slog.e(TAG, "Error parsing user-id " + userIdString); + } break; + } case TAG_DEVICE_INITIALIZER: mDeviceInitializer = OwnerInfo.readFromXml(parser); break; @@ -594,7 +611,6 @@ class Owners { pw.println(prefix + "admin=" + admin); pw.println(prefix + "name=" + name); pw.println(prefix + "package=" + packageName); - pw.println(); } } @@ -602,6 +618,17 @@ class Owners { if (mDeviceOwner != null) { pw.println(prefix + "Device Owner: "); mDeviceOwner.dump(prefix + " ", pw); + pw.println(prefix + " User ID: " + mDeviceOwnerUserId); + pw.println(); + } + if (mDeviceInitializer != null) { + pw.println(prefix + "Device Initializer: "); + mDeviceInitializer.dump(prefix + " ", pw); + pw.println(); + } + if (mSystemUpdatePolicy != null) { + pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy); + pw.println(); } if (mProfileOwners != null) { for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java index a284ca038e72..3b88fb165775 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.UserInfo; import android.os.FileUtils; +import android.os.UserHandle; import android.test.AndroidTestCase; import android.util.Log; @@ -37,11 +38,13 @@ import static org.mockito.Mockito.when; /** * Tests for the DeviceOwner object that saves & loads device and policy owner information. * run this test with: - mmma frameworks/base/services/tests/servicestests/ && + m FrameworksServicesTests && adb install \ -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && adb shell am instrument -e class com.android.server.devicepolicy.OwnersTest \ -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner + + (mmma frameworks/base/services/tests/servicestests/ for non-ninja build) */ public class OwnersTest extends DpmTestBase { private static final String TAG = "DeviceOwnerTest"; @@ -147,6 +150,12 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + assertFalse(owners.hasDeviceInitializer()); + assertNull(owners.getSystemUpdatePolicy()); + assertEquals(0, owners.getProfileOwnerKeys().size()); } // Then re-read and check. @@ -155,6 +164,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertNull(owners.getSystemUpdatePolicy()); assertEquals(0, owners.getProfileOwnerKeys().size()); @@ -181,6 +191,15 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertTrue(owners.hasDeviceOwner()); + assertEquals(null, owners.getDeviceOwnerName()); + assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); + + assertFalse(owners.hasDeviceInitializer()); + assertNull(owners.getSystemUpdatePolicy()); + assertEquals(0, owners.getProfileOwnerKeys().size()); } // Then re-read and check. @@ -191,6 +210,7 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.hasDeviceOwner()); assertEquals(null, owners.getDeviceOwnerName()); assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertNull(owners.getSystemUpdatePolicy()); @@ -218,6 +238,23 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + assertFalse(owners.hasDeviceInitializer()); + assertNull(owners.getSystemUpdatePolicy()); + + assertEquals(2, owners.getProfileOwnerKeys().size()); + assertEquals(new ComponentName("com.google.android.testdpc", + "com.google.android.testdpc.DeviceAdminReceiver0"), + owners.getProfileOwnerComponent(10)); + assertEquals("0", owners.getProfileOwnerName(10)); + assertEquals("com.google.android.testdpc", owners.getProfileOwnerPackage(10)); + + assertEquals(new ComponentName("com.google.android.testdpc1", ""), + owners.getProfileOwnerComponent(11)); + assertEquals("1", owners.getProfileOwnerName(11)); + assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); } // Then re-read and check. @@ -226,6 +263,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertNull(owners.getSystemUpdatePolicy()); @@ -263,6 +301,28 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(21).exists()); + + assertTrue(owners.hasDeviceOwner()); + assertEquals(null, owners.getDeviceOwnerName()); + assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); + + assertTrue(owners.hasDeviceInitializer()); + assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); + assertNotNull(owners.getSystemUpdatePolicy()); + assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType()); + + assertEquals(2, owners.getProfileOwnerKeys().size()); + assertEquals(new ComponentName("com.google.android.testdpc", + "com.google.android.testdpc.DeviceAdminReceiver0"), + owners.getProfileOwnerComponent(10)); + assertEquals("0", owners.getProfileOwnerName(10)); + assertEquals("com.google.android.testdpc", owners.getProfileOwnerPackage(10)); + + assertEquals(new ComponentName("com.google.android.testdpc1", ""), + owners.getProfileOwnerComponent(11)); + assertEquals("1", owners.getProfileOwnerName(11)); + assertEquals("com.google.android.testdpc1", owners.getProfileOwnerPackage(11)); } // Then re-read and check. @@ -273,6 +333,7 @@ public class OwnersTest extends DpmTestBase { assertTrue(owners.hasDeviceOwner()); assertEquals(null, owners.getDeviceOwnerName()); assertEquals("com.google.android.testdpc", owners.getDeviceOwnerPackageName()); + assertEquals(UserHandle.USER_SYSTEM, owners.getDeviceOwnerUserId()); assertTrue(owners.hasDeviceInitializer()); assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); @@ -312,6 +373,15 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + + assertTrue(owners.hasDeviceInitializer()); + assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); + + assertNull(owners.getSystemUpdatePolicy()); + assertEquals(0, owners.getProfileOwnerKeys().size()); } // Then re-read and check. @@ -320,6 +390,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertTrue(owners.hasDeviceInitializer()); assertEquals("com.google.android.testdpcx", owners.getDeviceInitializerPackageName()); @@ -348,6 +419,14 @@ public class OwnersTest extends DpmTestBase { assertFalse(owners.getProfileOwnerFileWithTestOverride(10).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(11).exists()); assertFalse(owners.getProfileOwnerFileWithTestOverride(20).exists()); + + assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); + assertFalse(owners.hasDeviceInitializer()); + assertEquals(0, owners.getProfileOwnerKeys().size()); + + assertNotNull(owners.getSystemUpdatePolicy()); + assertEquals(5, owners.getSystemUpdatePolicy().getPolicyType()); } // Then re-read and check. @@ -356,6 +435,7 @@ public class OwnersTest extends DpmTestBase { owners.load(); assertFalse(owners.hasDeviceOwner()); + assertEquals(UserHandle.USER_NULL, owners.getDeviceOwnerUserId()); assertFalse(owners.hasDeviceInitializer()); assertEquals(0, owners.getProfileOwnerKeys().size()); |