summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/dpm/src/com/android/commands/dpm/Dpm.java15
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java22
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java137
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/DirectoryFragmentModelTest.java157
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java7
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java33
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java58
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java18
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java112
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java82
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());