Merge "Move DisposableBroadcastReceiverAsUser"
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 05daf63..a87b133 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -147,119 +147,146 @@
@MainThread
@Override
public void executeMessage(Message msg) {
- InputMethod inputMethod = mInputMethod.get();
- // Need a valid reference to the inputMethod for everything except a dump.
- if (inputMethod == null && msg.what != DO_DUMP) {
- Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
- return;
- }
-
+ final InputMethod inputMethod = mInputMethod.get();
+ final InputMethodServiceInternal target = mTarget.get();
switch (msg.what) {
case DO_DUMP: {
- InputMethodServiceInternal target = mTarget.get();
- if (target == null) {
- return;
- }
SomeArgs args = (SomeArgs)msg.obj;
- try {
- target.dump((FileDescriptor) args.arg1,
- (PrintWriter) args.arg2, (String[]) args.arg3);
- } catch (RuntimeException e) {
- ((PrintWriter)args.arg2).println("Exception: " + e);
- }
- synchronized (args.arg4) {
- ((CountDownLatch)args.arg4).countDown();
+ if (isValid(inputMethod, target, "DO_DUMP")) {
+ final FileDescriptor fd = (FileDescriptor) args.arg1;
+ final PrintWriter fout = (PrintWriter) args.arg2;
+ final String[] dumpArgs = (String[]) args.arg3;
+ final CountDownLatch latch = (CountDownLatch) args.arg4;
+ try {
+ target.dump(fd, fout, dumpArgs);
+ } catch (RuntimeException e) {
+ fout.println("Exception: " + e);
+ } finally {
+ latch.countDown();
+ }
}
args.recycle();
return;
}
case DO_INITIALIZE_INTERNAL:
- inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
+ if (isValid(inputMethod, target, "DO_INITIALIZE_INTERNAL")) {
+ inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
+ }
return;
case DO_SET_INPUT_CONTEXT: {
- inputMethod.bindInput((InputBinding)msg.obj);
+ if (isValid(inputMethod, target, "DO_SET_INPUT_CONTEXT")) {
+ inputMethod.bindInput((InputBinding) msg.obj);
+ }
return;
}
case DO_UNSET_INPUT_CONTEXT:
- inputMethod.unbindInput();
+ if (isValid(inputMethod, target, "DO_UNSET_INPUT_CONTEXT")) {
+ inputMethod.unbindInput();
+ }
return;
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
- final InputConnection inputConnection = (InputConnection) args.arg1;
- final IInputMethod.StartInputParams params =
- (IInputMethod.StartInputParams) args.arg2;
- inputMethod.dispatchStartInput(inputConnection, params);
+ if (isValid(inputMethod, target, "DO_START_INPUT")) {
+ final InputConnection inputConnection = (InputConnection) args.arg1;
+ final IInputMethod.StartInputParams params =
+ (IInputMethod.StartInputParams) args.arg2;
+ inputMethod.dispatchStartInput(inputConnection, params);
+ }
args.recycle();
return;
}
case DO_ON_NAV_BUTTON_FLAGS_CHANGED:
- inputMethod.onNavButtonFlagsChanged(msg.arg1);
+ if (isValid(inputMethod, target, "DO_ON_NAV_BUTTON_FLAGS_CHANGED")) {
+ inputMethod.onNavButtonFlagsChanged(msg.arg1);
+ }
return;
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
- inputMethod.createSession(new InputMethodSessionCallbackWrapper(
- mContext, (InputChannel) args.arg1,
- (IInputMethodSessionCallback) args.arg2));
+ if (isValid(inputMethod, target, "DO_CREATE_SESSION")) {
+ inputMethod.createSession(new InputMethodSessionCallbackWrapper(
+ mContext, (InputChannel) args.arg1,
+ (IInputMethodSessionCallback) args.arg2));
+ }
args.recycle();
return;
}
case DO_SET_SESSION_ENABLED:
- inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
- msg.arg1 != 0);
+ if (isValid(inputMethod, target, "DO_SET_SESSION_ENABLED")) {
+ inputMethod.setSessionEnabled((InputMethodSession) msg.obj, msg.arg1 != 0);
+ }
return;
case DO_SHOW_SOFT_INPUT: {
final SomeArgs args = (SomeArgs)msg.obj;
- inputMethod.showSoftInputWithToken(
- msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
+ inputMethod.showSoftInputWithToken(
+ msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1);
+ }
args.recycle();
return;
}
case DO_HIDE_SOFT_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
- (IBinder) args.arg1);
+ if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
+ inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
+ (IBinder) args.arg1);
+ }
args.recycle();
return;
}
case DO_CHANGE_INPUTMETHOD_SUBTYPE:
- inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
+ if (isValid(inputMethod, target, "DO_CHANGE_INPUTMETHOD_SUBTYPE")) {
+ inputMethod.changeInputMethodSubtype((InputMethodSubtype) msg.obj);
+ }
return;
case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.onCreateInlineSuggestionsRequest(
- (InlineSuggestionsRequestInfo) args.arg1,
- (IInlineSuggestionsRequestCallback) args.arg2);
+ if (isValid(inputMethod, target, "DO_CREATE_INLINE_SUGGESTIONS_REQUEST")) {
+ inputMethod.onCreateInlineSuggestionsRequest(
+ (InlineSuggestionsRequestInfo) args.arg1,
+ (IInlineSuggestionsRequestCallback) args.arg2);
+ }
args.recycle();
return;
}
case DO_CAN_START_STYLUS_HANDWRITING: {
- inputMethod.canStartStylusHandwriting(msg.arg1);
+ if (isValid(inputMethod, target, "DO_CAN_START_STYLUS_HANDWRITING")) {
+ inputMethod.canStartStylusHandwriting(msg.arg1);
+ }
return;
}
case DO_UPDATE_TOOL_TYPE: {
- inputMethod.updateEditorToolType(msg.arg1);
+ if (isValid(inputMethod, target, "DO_UPDATE_TOOL_TYPE")) {
+ inputMethod.updateEditorToolType(msg.arg1);
+ }
return;
}
case DO_START_STYLUS_HANDWRITING: {
final SomeArgs args = (SomeArgs) msg.obj;
- inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
- (List<MotionEvent>) args.arg2);
+ if (isValid(inputMethod, target, "DO_START_STYLUS_HANDWRITING")) {
+ inputMethod.startStylusHandwriting(msg.arg1, (InputChannel) args.arg1,
+ (List<MotionEvent>) args.arg2);
+ }
args.recycle();
return;
}
case DO_INIT_INK_WINDOW: {
- inputMethod.initInkWindow();
+ if (isValid(inputMethod, target, "DO_INIT_INK_WINDOW")) {
+ inputMethod.initInkWindow();
+ }
return;
}
case DO_FINISH_STYLUS_HANDWRITING: {
- inputMethod.finishStylusHandwriting();
+ if (isValid(inputMethod, target, "DO_FINISH_STYLUS_HANDWRITING")) {
+ inputMethod.finishStylusHandwriting();
+ }
return;
}
case DO_REMOVE_STYLUS_HANDWRITING_WINDOW: {
- inputMethod.removeStylusHandwritingWindow();
+ if (isValid(inputMethod, target, "DO_REMOVE_STYLUS_HANDWRITING_WINDOW")) {
+ inputMethod.removeStylusHandwritingWindow();
+ }
return;
}
-
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -445,4 +472,15 @@
public void removeStylusHandwritingWindow() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_STYLUS_HANDWRITING_WINDOW));
}
+
+ private static boolean isValid(InputMethod inputMethod, InputMethodServiceInternal target,
+ String msg) {
+ if (inputMethod != null && target != null && !target.isServiceDestroyed()) {
+ return true;
+ } else {
+ Log.w(TAG, "Ignoring " + msg + ", InputMethod:" + inputMethod
+ + ", InputMethodServiceInternal:" + target);
+ return false;
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7436601..8b3451e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -700,11 +700,6 @@
@MainThread
@Override
public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
- if (mDestroyed) {
- Log.i(TAG, "The InputMethodService has already onDestroyed()."
- + "Ignore the initialization.");
- return;
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
mConfigTracker.onInitialize(params.configChanges);
mPrivOps.set(params.privilegedOperations);
@@ -3938,6 +3933,14 @@
public void triggerServiceDump(String where, @Nullable byte[] icProto) {
ImeTracing.getInstance().triggerServiceDump(where, mDumper, icProto);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isServiceDestroyed() {
+ return mDestroyed;
+ }
};
}
diff --git a/core/java/android/inputmethodservice/InputMethodServiceInternal.java b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
index f44f49d..c6612f6 100644
--- a/core/java/android/inputmethodservice/InputMethodServiceInternal.java
+++ b/core/java/android/inputmethodservice/InputMethodServiceInternal.java
@@ -85,4 +85,11 @@
*/
default void triggerServiceDump(@NonNull String where, @Nullable byte[] icProto) {
}
+
+ /**
+ * @return {@code true} if {@link InputMethodService} is destroyed.
+ */
+ default boolean isServiceDestroyed() {
+ return false;
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 41c0367..d67f94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -168,6 +168,17 @@
return new ShelfState();
}
+ @Override
+ public String toString() {
+ return "NotificationShelf("
+ + "hideBackground=" + mHideBackground + " notGoneIndex=" + mNotGoneIndex
+ + " hasItemsInStableShelf=" + mHasItemsInStableShelf
+ + " statusBarState=" + mStatusBarState + " interactive=" + mInteractive
+ + " animationsEnabled=" + mAnimationsEnabled
+ + " showNotificationShelf=" + mShowNotificationShelf
+ + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
+ }
+
/** Update the state of the shelf. */
public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 039a362..827d0d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -36,7 +36,6 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.os.Parcelable;
import android.os.Trace;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -551,9 +550,12 @@
}
}
+ @Override
public String toString() {
- return "StatusBarIconView(slot=" + mSlot + " icon=" + mIcon
- + " notification=" + mNotification + ")";
+ return "StatusBarIconView("
+ + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon
+ + " iconColor=#" + Integer.toHexString(mIconColor)
+ + " notification=" + mNotification + ')';
}
public StatusBarNotification getNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 7b8c5fc..5a70d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -278,6 +278,15 @@
}
}
+ @Override
+ public String toString() {
+ return "NotificationIconContainer("
+ + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+ + " inNotificationIconShelf=" + mInNotificationIconShelf
+ + " speedBumpIndex=" + mSpeedBumpIndex
+ + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+ }
+
@VisibleForTesting
public void setIconSize(int size) {
mIconSize = size;
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictions.java b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
new file mode 100644
index 0000000..f7ccd34
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictions.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.os.PackageTagsList;
+
+import java.io.PrintWriter;
+
+/**
+ * Legacy implementation for AppOpsService's app-op restrictions (global and user)
+ * storage and access.
+ */
+public interface AppOpsRestrictions {
+ /**
+ * Set or clear a global app-op restriction for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client this restriction applies to.
+ * @param code The app-op opCode to set (or clear) a restriction for.
+ * @param restricted {@code true} to restrict this app-op code, or {@code false} to clear an
+ * existing restriction.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean setGlobalRestriction(Object clientToken, int code, boolean restricted);
+
+ /**
+ * Get the state of a global app-op restriction for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to get the restriction state of.
+ * @param code The app-op code to get the restriction state of.
+ * @return the restriction state
+ */
+ boolean getGlobalRestriction(Object clientToken, int code);
+
+ /**
+ * Returns {@code true} if *any* global app-op restrictions are currently set for the given
+ * {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to check restrictions for.
+ * @return {@code true} if any restrictions are set
+ */
+ boolean hasGlobalRestrictions(Object clientToken);
+
+ /**
+ * Clear *all* global app-op restrictions for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to clear restrictions from.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean clearGlobalRestrictions(Object clientToken);
+
+ /**
+ * Set or clear a user app-op restriction for the given {@code clientToken} and {@code userId}.
+ *
+ * @param clientToken A token identifying the client this restriction applies to.
+ * @param code The app-op code to set (or clear) a restriction for.
+ * @param restricted {@code true} to restrict this app-op code, or {@code false} to
+ * remove any existing restriction.
+ * @param excludedPackageTags A list of packages and associated attribution tags to exclude
+ * from this restriction. Or, if {@code null}, removes any
+ * exclusions from this restriction.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean setUserRestriction(Object clientToken, int userId, int code, boolean restricted,
+ PackageTagsList excludedPackageTags);
+
+ /**
+ * Get the state of a user app-op restriction for the given {@code clientToken} and {@code
+ * userId}. Or, if the combination of ({{@code clientToken}, {@code userId}, @code
+ * packageName}, {@code attributionTag}) has been excluded via
+ * {@link AppOpsRestrictions#setUserRestriction}, always returns {@code false}.
+ *
+ * @param clientToken A token identifying the client this restriction applies to.
+ * @param userId Which userId this restriction applies to.
+ * @param code The app-op code to get the restriction state of.
+ * @param packageName A package name used to check for exclusions.
+ * @param attributionTag An attribution tag used to check for exclusions.
+ * @param isCheckOp a flag that, when {@code true}, denotes that exclusions should be
+ * checked by (packageName) rather than (packageName, attributionTag)
+ * @return the restriction state
+ */
+ boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
+ String attributionTag, boolean isCheckOp);
+
+ /**
+ * Returns {@code true} if *any* user app-op restrictions are currently set for the given
+ * {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to check restrictions for.
+ * @return {@code true} if any restrictions are set
+ */
+ boolean hasUserRestrictions(Object clientToken);
+
+ /**
+ * Clear *all* user app-op restrictions for the given {@code clientToken}.
+ *
+ * @param clientToken A token identifying the client to clear restrictions for.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean clearUserRestrictions(Object clientToken);
+
+ /**
+ * Clear *all* user app-op restrictions for the given {@code clientToken} and {@code userId}.
+ *
+ * @param clientToken A token identifying the client to clear restrictions for.
+ * @param userId Which userId to clear restrictions for.
+ * @return {@code true} if any restriction state was modified as a result of this operation
+ */
+ boolean clearUserRestrictions(Object clientToken, Integer userId);
+
+ /**
+ * Returns the set of exclusions previously set by
+ * {@link AppOpsRestrictions#setUserRestriction} for the given {@code clientToken}
+ * and {@code userId}.
+ *
+ * @param clientToken A token identifying the client to get restriction exclusions for.
+ * @param userId Which userId to get restriction exclusions for
+ * @return a set of user restriction exclusions
+ */
+ PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId);
+
+ /**
+ * Dump the state of appop restrictions.
+ *
+ * @param printWriter writer to dump to.
+ * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's
+ * set to an app-op, only the watchers for that app-op are dumped.
+ * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package
+ * name.
+ * @param showUserRestrictions include user restriction state in the output
+ */
+ void dumpRestrictions(PrintWriter printWriter, int dumpOp, String dumpPackage,
+ boolean showUserRestrictions);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
new file mode 100644
index 0000000..adfd2af
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.annotation.RequiresPermission;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.PackageTagsList;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Implementation for AppOpsService's app-op restrictions (global and user) storage and retrieval.
+ */
+public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
+
+ private static final int UID_ANY = -2;
+
+ private Context mContext;
+ private Handler mHandler;
+ private AppOpsServiceInterface mAppOpsServiceInterface;
+
+ // Map from (Object token) to (int code) to (boolean restricted)
+ private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
+
+ // Map from (Object token) to (int userId) to (int code) to (boolean restricted)
+ private final ArrayMap<Object, SparseArray<SparseBooleanArray>> mUserRestrictions =
+ new ArrayMap<>();
+
+ // Map from (Object token) to (int userId) to (PackageTagsList packageTagsList)
+ private final ArrayMap<Object, SparseArray<PackageTagsList>>
+ mUserRestrictionExcludedPackageTags = new ArrayMap<>();
+
+ public AppOpsRestrictionsImpl(Context context, Handler handler,
+ AppOpsServiceInterface appOpsServiceInterface) {
+ mContext = context;
+ mHandler = handler;
+ mAppOpsServiceInterface = appOpsServiceInterface;
+ }
+
+ @Override
+ public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+ if (restricted) {
+ if (!mGlobalRestrictions.containsKey(clientToken)) {
+ mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
+ }
+ SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+ Objects.requireNonNull(restrictedCodes);
+ boolean changed = !restrictedCodes.get(code);
+ restrictedCodes.put(code, true);
+ return changed;
+ } else {
+ SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ boolean changed = restrictedCodes.get(code);
+ restrictedCodes.delete(code);
+ if (restrictedCodes.size() == 0) {
+ mGlobalRestrictions.remove(clientToken);
+ }
+ return changed;
+ }
+ }
+
+ @Override
+ public boolean getGlobalRestriction(Object clientToken, int code) {
+ SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ return restrictedCodes.get(code);
+ }
+
+ @Override
+ public boolean hasGlobalRestrictions(Object clientToken) {
+ return mGlobalRestrictions.containsKey(clientToken);
+ }
+
+ @Override
+ public boolean clearGlobalRestrictions(Object clientToken) {
+ return mGlobalRestrictions.remove(clientToken) != null;
+ }
+
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ @Override
+ public boolean setUserRestriction(Object clientToken, int userId, int code,
+ boolean restricted,
+ PackageTagsList excludedPackageTags) {
+ int[] userIds = resolveUserId(userId);
+ boolean changed = false;
+ for (int i = 0; i < userIds.length; i++) {
+ changed |= putUserRestriction(clientToken, userIds[i], code, restricted);
+ changed |= putUserRestrictionExclusions(clientToken, userIds[i],
+ excludedPackageTags);
+ }
+ return changed;
+ }
+
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS
+ })
+ private int[] resolveUserId(int userId) {
+ int[] userIds;
+ if (userId == UserHandle.USER_ALL) {
+ // TODO(b/162888972): this call is returning all users, not just live ones - we
+ // need to either fix the method called, or rename the variable
+ List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
+
+ userIds = new int[liveUsers.size()];
+ for (int i = 0; i < liveUsers.size(); i++) {
+ userIds[i] = liveUsers.get(i).id;
+ }
+ } else {
+ userIds = new int[]{userId};
+ }
+ return userIds;
+ }
+
+ @Override
+ public boolean hasUserRestrictions(Object clientToken) {
+ return mUserRestrictions.containsKey(clientToken);
+ }
+
+ private boolean getUserRestriction(Object clientToken, int userId, int code) {
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes =
+ mUserRestrictions.get(clientToken);
+ if (userIdRestrictedCodes == null) {
+ return false;
+ }
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ return restrictedCodes.get(code);
+ }
+
+ @Override
+ public boolean getUserRestriction(Object clientToken, int userId, int code, String packageName,
+ String attributionTag, boolean isCheckOp) {
+ boolean restricted = getUserRestriction(clientToken, userId, code);
+ if (!restricted) {
+ return false;
+ }
+
+ PackageTagsList perUserExclusions = getUserRestrictionExclusions(clientToken, userId);
+ if (perUserExclusions == null) {
+ return true;
+ }
+
+ // TODO (b/240617242) add overload for checkOp to support attribution tags
+ if (isCheckOp) {
+ return !perUserExclusions.includes(packageName);
+ }
+ return !perUserExclusions.contains(packageName, attributionTag);
+ }
+
+ @Override
+ public boolean clearUserRestrictions(Object clientToken) {
+ boolean changed = false;
+ SparseBooleanArray allUserRestrictedCodes = collectAllUserRestrictedCodes(clientToken);
+ changed |= mUserRestrictions.remove(clientToken) != null;
+ changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
+ notifyAllUserRestrictions(allUserRestrictedCodes);
+ return changed;
+ }
+
+ private SparseBooleanArray collectAllUserRestrictedCodes(Object clientToken) {
+ SparseBooleanArray allRestrictedCodes = new SparseBooleanArray();
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(clientToken);
+ if (userIdRestrictedCodes == null) {
+ return allRestrictedCodes;
+ }
+ int userIdRestrictedCodesSize = userIdRestrictedCodes.size();
+ for (int i = 0; i < userIdRestrictedCodesSize; i++) {
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.valueAt(i);
+ int restrictedCodesSize = restrictedCodes.size();
+ for (int j = 0; j < restrictedCodesSize; j++) {
+ int code = restrictedCodes.keyAt(j);
+ allRestrictedCodes.put(code, true);
+ }
+ }
+ return allRestrictedCodes;
+ }
+
+ // TODO: For clearUserRestrictions, we are calling notifyOpChanged from within the
+ // LegacyAppOpsServiceInterfaceImpl class. But, for all other changes to restrictions, we're
+ // calling it from within AppOpsService. This is awkward, and we should probably do it one
+ // way or the other.
+ private void notifyAllUserRestrictions(SparseBooleanArray allUserRestrictedCodes) {
+ int restrictedCodesSize = allUserRestrictedCodes.size();
+ for (int j = 0; j < restrictedCodesSize; j++) {
+ int code = allUserRestrictedCodes.keyAt(j);
+ mHandler.post(() -> mAppOpsServiceInterface.notifyWatchersOfChange(code, UID_ANY));
+ }
+ }
+
+ @Override
+ public boolean clearUserRestrictions(Object clientToken, Integer userId) {
+ boolean changed = false;
+
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes =
+ mUserRestrictions.get(clientToken);
+ if (userIdRestrictedCodes != null) {
+ changed |= userIdRestrictedCodes.contains(userId);
+ userIdRestrictedCodes.remove(userId);
+ if (userIdRestrictedCodes.size() == 0) {
+ mUserRestrictions.remove(clientToken);
+ }
+ }
+
+ SparseArray<PackageTagsList> userIdPackageTags =
+ mUserRestrictionExcludedPackageTags.get(clientToken);
+ if (userIdPackageTags != null) {
+ changed |= userIdPackageTags.contains(userId);
+ userIdPackageTags.remove(userId);
+ if (userIdPackageTags.size() == 0) {
+ mUserRestrictionExcludedPackageTags.remove(clientToken);
+ }
+ }
+
+ return changed;
+ }
+
+ private boolean putUserRestriction(Object token, int userId, int code, boolean restricted) {
+ boolean changed = false;
+ if (restricted) {
+ if (!mUserRestrictions.containsKey(token)) {
+ mUserRestrictions.put(token, new SparseArray<>());
+ }
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
+ Objects.requireNonNull(userIdRestrictedCodes);
+
+ if (!userIdRestrictedCodes.contains(userId)) {
+ userIdRestrictedCodes.put(userId, new SparseBooleanArray());
+ }
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+
+ changed = !restrictedCodes.get(code);
+ restrictedCodes.put(code, restricted);
+ } else {
+ SparseArray<SparseBooleanArray> userIdRestrictedCodes = mUserRestrictions.get(token);
+ if (userIdRestrictedCodes == null) {
+ return false;
+ }
+ SparseBooleanArray restrictedCodes = userIdRestrictedCodes.get(userId);
+ if (restrictedCodes == null) {
+ return false;
+ }
+ changed = restrictedCodes.get(code);
+ restrictedCodes.delete(code);
+ if (restrictedCodes.size() == 0) {
+ userIdRestrictedCodes.remove(userId);
+ }
+ if (userIdRestrictedCodes.size() == 0) {
+ mUserRestrictions.remove(token);
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public PackageTagsList getUserRestrictionExclusions(Object clientToken, int userId) {
+ SparseArray<PackageTagsList> userIdPackageTags =
+ mUserRestrictionExcludedPackageTags.get(clientToken);
+ if (userIdPackageTags == null) {
+ return null;
+ }
+ return userIdPackageTags.get(userId);
+ }
+
+ private boolean putUserRestrictionExclusions(Object token, int userId,
+ PackageTagsList excludedPackageTags) {
+ boolean addingExclusions = excludedPackageTags != null && !excludedPackageTags.isEmpty();
+ if (addingExclusions) {
+ if (!mUserRestrictionExcludedPackageTags.containsKey(token)) {
+ mUserRestrictionExcludedPackageTags.put(token, new SparseArray<>());
+ }
+ SparseArray<PackageTagsList> userIdExcludedPackageTags =
+ mUserRestrictionExcludedPackageTags.get(token);
+ Objects.requireNonNull(userIdExcludedPackageTags);
+
+ userIdExcludedPackageTags.put(userId, excludedPackageTags);
+ return true;
+ } else {
+ SparseArray<PackageTagsList> userIdExclusions =
+ mUserRestrictionExcludedPackageTags.get(token);
+ if (userIdExclusions == null) {
+ return false;
+ }
+ boolean changed = userIdExclusions.get(userId) != null;
+ userIdExclusions.remove(userId);
+ if (userIdExclusions.size() == 0) {
+ mUserRestrictionExcludedPackageTags.remove(token);
+ }
+ return changed;
+ }
+ }
+
+ @Override
+ public void dumpRestrictions(PrintWriter pw, int code, String dumpPackage,
+ boolean showUserRestrictions) {
+ final int globalRestrictionCount = mGlobalRestrictions.size();
+ for (int i = 0; i < globalRestrictionCount; i++) {
+ Object token = mGlobalRestrictions.keyAt(i);
+ SparseBooleanArray restrictedOps = mGlobalRestrictions.valueAt(i);
+
+ pw.println(" Global restrictions for token " + token + ":");
+ StringBuilder restrictedOpsValue = new StringBuilder();
+ restrictedOpsValue.append("[");
+ final int restrictedOpCount = restrictedOps.size();
+ for (int j = 0; j < restrictedOpCount; j++) {
+ if (restrictedOpsValue.length() > 1) {
+ restrictedOpsValue.append(", ");
+ }
+ restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.keyAt(j)));
+ }
+ restrictedOpsValue.append("]");
+ pw.println(" Restricted ops: " + restrictedOpsValue);
+ }
+
+ if (!showUserRestrictions) {
+ return;
+ }
+
+ final int userRestrictionCount = mUserRestrictions.size();
+ for (int i = 0; i < userRestrictionCount; i++) {
+ Object token = mUserRestrictions.keyAt(i);
+ SparseArray<SparseBooleanArray> perUserRestrictions = mUserRestrictions.get(token);
+ SparseArray<PackageTagsList> perUserExcludedPackageTags =
+ mUserRestrictionExcludedPackageTags.get(token);
+
+ boolean printedTokenHeader = false;
+
+ final int restrictionCount = perUserRestrictions != null
+ ? perUserRestrictions.size() : 0;
+ if (restrictionCount > 0 && dumpPackage == null) {
+ boolean printedOpsHeader = false;
+ for (int j = 0; j < restrictionCount; j++) {
+ int userId = perUserRestrictions.keyAt(j);
+ SparseBooleanArray restrictedOps = perUserRestrictions.valueAt(j);
+ if (restrictedOps == null) {
+ continue;
+ }
+ if (code >= 0 && !restrictedOps.get(code)) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ pw.println(" User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+ if (!printedOpsHeader) {
+ pw.println(" Restricted ops:");
+ printedOpsHeader = true;
+ }
+ StringBuilder restrictedOpsValue = new StringBuilder();
+ restrictedOpsValue.append("[");
+ final int restrictedOpCount = restrictedOps.size();
+ for (int k = 0; k < restrictedOpCount; k++) {
+ int restrictedOp = restrictedOps.keyAt(k);
+ if (restrictedOpsValue.length() > 1) {
+ restrictedOpsValue.append(", ");
+ }
+ restrictedOpsValue.append(AppOpsManager.opToName(restrictedOp));
+ }
+ restrictedOpsValue.append("]");
+ pw.print(" ");
+ pw.print("user: ");
+ pw.print(userId);
+ pw.print(" restricted ops: ");
+ pw.println(restrictedOpsValue);
+ }
+ }
+
+ final int excludedPackageCount = perUserExcludedPackageTags != null
+ ? perUserExcludedPackageTags.size() : 0;
+ if (excludedPackageCount > 0 && code < 0) {
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.increaseIndent();
+ boolean printedPackagesHeader = false;
+ for (int j = 0; j < excludedPackageCount; j++) {
+ int userId = perUserExcludedPackageTags.keyAt(j);
+ PackageTagsList packageNames =
+ perUserExcludedPackageTags.valueAt(j);
+ if (packageNames == null) {
+ continue;
+ }
+ boolean hasPackage;
+ if (dumpPackage != null) {
+ hasPackage = packageNames.includes(dumpPackage);
+ } else {
+ hasPackage = true;
+ }
+ if (!hasPackage) {
+ continue;
+ }
+ if (!printedTokenHeader) {
+ ipw.println("User restrictions for token " + token + ":");
+ printedTokenHeader = true;
+ }
+
+ ipw.increaseIndent();
+ if (!printedPackagesHeader) {
+ ipw.println("Excluded packages:");
+ printedPackagesHeader = true;
+ }
+
+ ipw.increaseIndent();
+ ipw.print("user: ");
+ ipw.print(userId);
+ ipw.println(" packages: ");
+
+ ipw.increaseIndent();
+ packageNames.dump(ipw);
+
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index b844fc6..072d17f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -97,7 +97,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
-import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
import android.net.Uri;
@@ -118,14 +117,12 @@
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.storage.StorageManagerInternal;
import android.permission.PermissionManager;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.IndentingPrintWriter;
import android.util.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
@@ -367,6 +364,9 @@
/** Interface for app-op modes.*/
@VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
+ /** Interface for app-op restrictions.*/
+ @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
+
private AppOpsUidStateTracker mUidStateTracker;
/** Hands the definition of foreground and uid states */
@@ -926,6 +926,8 @@
}
mAppOpsServiceInterface =
new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+ mAppOpsServiceInterface);
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
@@ -5348,124 +5350,8 @@
pw.println();
}
- final int globalRestrictionCount = mOpGlobalRestrictions.size();
- for (int i = 0; i < globalRestrictionCount; i++) {
- IBinder token = mOpGlobalRestrictions.keyAt(i);
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
- ArraySet<Integer> restrictedOps = restrictionState.mRestrictedOps;
-
- pw.println(" Global restrictions for token " + token + ":");
- StringBuilder restrictedOpsValue = new StringBuilder();
- restrictedOpsValue.append("[");
- final int restrictedOpCount = restrictedOps.size();
- for (int j = 0; j < restrictedOpCount; j++) {
- if (restrictedOpsValue.length() > 1) {
- restrictedOpsValue.append(", ");
- }
- restrictedOpsValue.append(AppOpsManager.opToName(restrictedOps.valueAt(j)));
- }
- restrictedOpsValue.append("]");
- pw.println(" Restricted ops: " + restrictedOpsValue);
-
- }
-
- final int userRestrictionCount = mOpUserRestrictions.size();
- for (int i = 0; i < userRestrictionCount; i++) {
- IBinder token = mOpUserRestrictions.keyAt(i);
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- boolean printedTokenHeader = false;
-
- if (dumpMode >= 0 || dumpWatchers || dumpHistory) {
- continue;
- }
-
- final int restrictionCount = restrictionState.perUserRestrictions != null
- ? restrictionState.perUserRestrictions.size() : 0;
- if (restrictionCount > 0 && dumpPackage == null) {
- boolean printedOpsHeader = false;
- for (int j = 0; j < restrictionCount; j++) {
- int userId = restrictionState.perUserRestrictions.keyAt(j);
- boolean[] restrictedOps = restrictionState.perUserRestrictions.valueAt(j);
- if (restrictedOps == null) {
- continue;
- }
- if (dumpOp >= 0 && (dumpOp >= restrictedOps.length
- || !restrictedOps[dumpOp])) {
- continue;
- }
- if (!printedTokenHeader) {
- pw.println(" User restrictions for token " + token + ":");
- printedTokenHeader = true;
- }
- if (!printedOpsHeader) {
- pw.println(" Restricted ops:");
- printedOpsHeader = true;
- }
- StringBuilder restrictedOpsValue = new StringBuilder();
- restrictedOpsValue.append("[");
- final int restrictedOpCount = restrictedOps.length;
- for (int k = 0; k < restrictedOpCount; k++) {
- if (restrictedOps[k]) {
- if (restrictedOpsValue.length() > 1) {
- restrictedOpsValue.append(", ");
- }
- restrictedOpsValue.append(AppOpsManager.opToName(k));
- }
- }
- restrictedOpsValue.append("]");
- pw.print(" "); pw.print("user: "); pw.print(userId);
- pw.print(" restricted ops: "); pw.println(restrictedOpsValue);
- }
- }
-
- final int excludedPackageCount = restrictionState.perUserExcludedPackageTags != null
- ? restrictionState.perUserExcludedPackageTags.size() : 0;
- if (excludedPackageCount > 0 && dumpOp < 0) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
- ipw.increaseIndent();
- boolean printedPackagesHeader = false;
- for (int j = 0; j < excludedPackageCount; j++) {
- int userId = restrictionState.perUserExcludedPackageTags.keyAt(j);
- PackageTagsList packageNames =
- restrictionState.perUserExcludedPackageTags.valueAt(j);
- if (packageNames == null) {
- continue;
- }
- boolean hasPackage;
- if (dumpPackage != null) {
- hasPackage = packageNames.includes(dumpPackage);
- } else {
- hasPackage = true;
- }
- if (!hasPackage) {
- continue;
- }
- if (!printedTokenHeader) {
- ipw.println("User restrictions for token " + token + ":");
- printedTokenHeader = true;
- }
-
- ipw.increaseIndent();
- if (!printedPackagesHeader) {
- ipw.println("Excluded packages:");
- printedPackagesHeader = true;
- }
-
- ipw.increaseIndent();
- ipw.print("user: ");
- ipw.print(userId);
- ipw.println(" packages: ");
-
- ipw.increaseIndent();
- packageNames.dump(ipw);
-
- ipw.decreaseIndent();
- ipw.decreaseIndent();
- ipw.decreaseIndent();
- }
- ipw.decreaseIndent();
- }
- }
+ boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+ mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
if (!dumpHistory && !dumpWatchers) {
pw.println();
@@ -6085,8 +5971,6 @@
private final class ClientUserRestrictionState implements DeathRecipient {
private final IBinder token;
- SparseArray<boolean[]> perUserRestrictions;
- SparseArray<PackageTagsList> perUserExcludedPackageTags;
ClientUserRestrictionState(IBinder token)
throws RemoteException {
@@ -6096,134 +5980,29 @@
public boolean setRestriction(int code, boolean restricted,
PackageTagsList excludedPackageTags, int userId) {
- boolean changed = false;
-
- if (perUserRestrictions == null && restricted) {
- perUserRestrictions = new SparseArray<>();
- }
-
- int[] users;
- if (userId == UserHandle.USER_ALL) {
- // TODO(b/162888972): this call is returning all users, not just live ones - we
- // need to either fix the method called, or rename the variable
- List<UserInfo> liveUsers = UserManager.get(mContext).getUsers();
-
- users = new int[liveUsers.size()];
- for (int i = 0; i < liveUsers.size(); i++) {
- users[i] = liveUsers.get(i).id;
- }
- } else {
- users = new int[]{userId};
- }
-
- if (perUserRestrictions != null) {
- int numUsers = users.length;
-
- for (int i = 0; i < numUsers; i++) {
- int thisUserId = users[i];
-
- boolean[] userRestrictions = perUserRestrictions.get(thisUserId);
- if (userRestrictions == null && restricted) {
- userRestrictions = new boolean[AppOpsManager._NUM_OP];
- perUserRestrictions.put(thisUserId, userRestrictions);
- }
- if (userRestrictions != null && userRestrictions[code] != restricted) {
- userRestrictions[code] = restricted;
- if (!restricted && isDefault(userRestrictions)) {
- perUserRestrictions.remove(thisUserId);
- userRestrictions = null;
- }
- changed = true;
- }
-
- if (userRestrictions != null) {
- final boolean noExcludedPackages =
- excludedPackageTags == null || excludedPackageTags.isEmpty();
- if (perUserExcludedPackageTags == null && !noExcludedPackages) {
- perUserExcludedPackageTags = new SparseArray<>();
- }
- if (perUserExcludedPackageTags != null) {
- if (noExcludedPackages) {
- perUserExcludedPackageTags.remove(thisUserId);
- if (perUserExcludedPackageTags.size() <= 0) {
- perUserExcludedPackageTags = null;
- }
- } else {
- perUserExcludedPackageTags.put(thisUserId, excludedPackageTags);
- }
- changed = true;
- }
- }
- }
- }
-
- return changed;
+ return mAppOpsRestrictions.setUserRestriction(token, userId, code,
+ restricted, excludedPackageTags);
}
- public boolean hasRestriction(int restriction, String packageName, String attributionTag,
+ public boolean hasRestriction(int code, String packageName, String attributionTag,
int userId, boolean isCheckOp) {
- if (perUserRestrictions == null) {
- return false;
- }
- boolean[] restrictions = perUserRestrictions.get(userId);
- if (restrictions == null) {
- return false;
- }
- if (!restrictions[restriction]) {
- return false;
- }
- if (perUserExcludedPackageTags == null) {
- return true;
- }
- PackageTagsList perUserExclusions = perUserExcludedPackageTags.get(userId);
- if (perUserExclusions == null) {
- return true;
- }
-
- // TODO (b/240617242) add overload for checkOp to support attribution tags
- if (isCheckOp) {
- return !perUserExclusions.includes(packageName);
- }
- return !perUserExclusions.contains(packageName, attributionTag);
+ return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
+ attributionTag, isCheckOp);
}
public void removeUser(int userId) {
- if (perUserExcludedPackageTags != null) {
- perUserExcludedPackageTags.remove(userId);
- if (perUserExcludedPackageTags.size() <= 0) {
- perUserExcludedPackageTags = null;
- }
- }
- if (perUserRestrictions != null) {
- perUserRestrictions.remove(userId);
- if (perUserRestrictions.size() <= 0) {
- perUserRestrictions = null;
- }
- }
+ mAppOpsRestrictions.clearUserRestrictions(token, userId);
}
public boolean isDefault() {
- return perUserRestrictions == null || perUserRestrictions.size() <= 0;
+ return !mAppOpsRestrictions.hasUserRestrictions(token);
}
@Override
public void binderDied() {
synchronized (AppOpsService.this) {
+ mAppOpsRestrictions.clearUserRestrictions(token);
mOpUserRestrictions.remove(token);
- if (perUserRestrictions == null) {
- return;
- }
- final int userCount = perUserRestrictions.size();
- for (int i = 0; i < userCount; i++) {
- final boolean[] restrictions = perUserRestrictions.valueAt(i);
- final int restrictionCount = restrictions.length;
- for (int j = 0; j < restrictionCount; j++) {
- if (restrictions[j]) {
- final int changedCode = j;
- mHandler.post(() -> notifyWatchersOfChange(changedCode, UID_ANY));
- }
- }
- }
destroy();
}
}
@@ -6231,23 +6010,10 @@
public void destroy() {
token.unlinkToDeath(this, 0);
}
-
- private boolean isDefault(boolean[] array) {
- if (ArrayUtils.isEmpty(array)) {
- return true;
- }
- for (boolean value : array) {
- if (value) {
- return false;
- }
- }
- return true;
- }
}
private final class ClientGlobalRestrictionState implements DeathRecipient {
final IBinder mToken;
- final ArraySet<Integer> mRestrictedOps = new ArraySet<>();
ClientGlobalRestrictionState(IBinder token)
throws RemoteException {
@@ -6256,23 +6022,21 @@
}
boolean setRestriction(int code, boolean restricted) {
- if (restricted) {
- return mRestrictedOps.add(code);
- } else {
- return mRestrictedOps.remove(code);
- }
+ return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
}
boolean hasRestriction(int code) {
- return mRestrictedOps.contains(code);
+ return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
}
boolean isDefault() {
- return mRestrictedOps.isEmpty();
+ return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
}
@Override
public void binderDied() {
+ mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+ mOpGlobalRestrictions.remove(mToken);
destroy();
}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index c4a9a4b..18f659e 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -148,6 +148,14 @@
/**
* Temporary API which will be removed once we can safely untangle the methods that use this.
* Notify that the app-op's mode is changed by triggering the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+ */
+ void notifyWatchersOfChange(int op, int uid);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
* @param changedListener the change listener.
* @param op App-op whose mode has changed
* @param uid user id associated with the app-op
@@ -198,5 +206,4 @@
* @param printWriter writer to dump to.
*/
boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
-
}
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
index 80266972..f6fff35 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
@@ -333,6 +333,18 @@
}
@Override
+ public void notifyWatchersOfChange(int code, int uid) {
+ ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
+ if (listenerSet == null) {
+ return;
+ }
+ for (int i = 0; i < listenerSet.size(); i++) {
+ final OnOpModeChangedListener listener = listenerSet.valueAt(i);
+ notifyOpChanged(listener, code, uid, null);
+ }
+ }
+
+ @Override
public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
int uid, @Nullable String packageName) {
Objects.requireNonNull(onModeChangedListener);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
new file mode 100644
index 0000000..5dc1251
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.quality.Strictness;
+
+public class AppOpsLegacyRestrictionsTest {
+ private static final int UID_ANY = -2;
+
+ final Object mClientToken = new Object();
+ final int mUserId1 = 65001;
+ final int mUserId2 = 65002;
+ final int mOpCode1 = OP_COARSE_LOCATION;
+ final int mOpCode2 = OP_FINE_LOCATION;
+ final String mPackageName = "com.example.test";
+ final String mAttributionTag = "test-attribution-tag";
+
+ StaticMockitoSession mSession;
+
+ @Mock
+ AppOpsService.Constants mConstants;
+
+ @Mock
+ Context mContext;
+
+ @Mock
+ Handler mHandler;
+
+ @Mock
+ AppOpsServiceInterface mLegacyAppOpsService;
+
+ AppOpsRestrictions mAppOpsRestrictions;
+
+ @Before
+ public void setUp() {
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
+ mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
+ mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
+ Mockito.when(mHandler.post(Mockito.any(Runnable.class))).then(inv -> {
+ Runnable r = inv.getArgument(0);
+ r.run();
+ return true;
+ });
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(mContext, mHandler, mLegacyAppOpsService);
+ }
+
+ @After
+ public void tearDown() {
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testSetAndGetSingleGlobalRestriction() {
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+ // Act: add a restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ // Act: add same restriction again (expect false; should be no-op)
+ assertEquals(false, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ assertEquals(true, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+ // Act: remove the restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+ // Act: remove same restriction again (expect false; should be no-op)
+ assertEquals(false,
+ mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getGlobalRestriction(mClientToken, mOpCode1));
+ }
+
+ @Test
+ public void testSetAndGetDoubleGlobalRestriction() {
+ // Act: add opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ // Act: add opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: remove opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, false));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: remove opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, false));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testClearGlobalRestrictions() {
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+ // Act: add opCodes
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode1, true));
+ assertEquals(true, mAppOpsRestrictions.setGlobalRestriction(mClientToken, mOpCode2, true));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: clear
+ assertEquals(true, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasGlobalRestrictions(mClientToken));
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearGlobalRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testSetAndGetSingleUserRestriction() {
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ // Act: add a restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ // Act: add the restriction again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ // Act: remove the restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, false, null));
+ // Act: remove the restriction again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, false, null));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ }
+
+ @Test
+ public void testSetAndGetDoubleUserRestriction() {
+ // Act: add opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ // Act: add opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+ // Act: remove opCode1 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, false, null));
+ // Verify: opCode1 is removed but not opCode22
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, true));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+ // Act: remove opCode2 restriction
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, false, null));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, false));
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode2, mPackageName, mAttributionTag, true));
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testClearUserRestrictionsAllUsers() {
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
+ // Act: add restrictions
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode2, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ // Act: clear all user restrictions
+ assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testClearUserRestrictionsSpecificUsers() {
+ // Act: clear (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+ // Act: add restrictions
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId1, mOpCode2, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode1, true, null));
+ assertEquals(true, mAppOpsRestrictions.setUserRestriction(
+ mClientToken, mUserId2, mOpCode2, true, null));
+ // Verify: not empty
+ assertEquals(true, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ // Act: clear userId1
+ assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+ // Act: clear userId1 again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId1));
+ // Verify: userId1 is removed but not userId2
+ assertEquals(false, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId1, mOpCode1, mPackageName, mAttributionTag, false));
+ assertEquals(true, mAppOpsRestrictions.getUserRestriction(
+ mClientToken, mUserId2, mOpCode2, mPackageName, mAttributionTag, false));
+ // Act: clear userId2
+ assertEquals(true, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
+ // Act: clear userId2 again (should be no-op)
+ assertEquals(false, mAppOpsRestrictions.clearUserRestrictions(mClientToken, mUserId2));
+ // Verify: empty
+ assertEquals(false, mAppOpsRestrictions.hasUserRestrictions(mClientToken));
+ }
+
+ @Test
+ public void testNotify() {
+ mAppOpsRestrictions.setUserRestriction(mClientToken, mUserId1, mOpCode1, true, null);
+ mAppOpsRestrictions.clearUserRestrictions(mClientToken);
+ Mockito.verify(mLegacyAppOpsService, Mockito.times(1))
+ .notifyWatchersOfChange(mOpCode1, UID_ANY);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
new file mode 100644
index 0000000..ad47773
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.view.KeyEvent.KEYCODE_A;
+import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
+import static android.view.KeyEvent.KEYCODE_B;
+import static android.view.KeyEvent.KEYCODE_C;
+import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
+import static android.view.KeyEvent.KEYCODE_E;
+import static android.view.KeyEvent.KEYCODE_L;
+import static android.view.KeyEvent.KEYCODE_M;
+import static android.view.KeyEvent.KEYCODE_META_LEFT;
+import static android.view.KeyEvent.KEYCODE_N;
+import static android.view.KeyEvent.KEYCODE_P;
+import static android.view.KeyEvent.KEYCODE_S;
+import static android.view.KeyEvent.KEYCODE_SLASH;
+import static android.view.KeyEvent.KEYCODE_SPACE;
+import static android.view.KeyEvent.KEYCODE_TAB;
+import static android.view.KeyEvent.KEYCODE_Z;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import org.junit.Test;
+
+public class ModifierShortcutTests extends ShortcutKeyTestBase {
+ private static final SparseArray<String> META_SHORTCUTS = new SparseArray<>();
+ static {
+ META_SHORTCUTS.append(KEYCODE_A, Intent.CATEGORY_APP_CALCULATOR);
+ META_SHORTCUTS.append(KEYCODE_B, Intent.CATEGORY_APP_BROWSER);
+ META_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
+ META_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
+ META_SHORTCUTS.append(KEYCODE_L, Intent.CATEGORY_APP_CALENDAR);
+ META_SHORTCUTS.append(KEYCODE_M, Intent.CATEGORY_APP_MAPS);
+ META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
+ META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
+ }
+
+ /**
+ * Test meta+ shortcuts defined in bookmarks.xml.
+ */
+ @Test
+ public void testMetaShortcuts() {
+ for (int i = 0; i < META_SHORTCUTS.size(); i++) {
+ final int keyCode = META_SHORTCUTS.keyAt(i);
+ final String category = META_SHORTCUTS.valueAt(i);
+
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, keyCode}, 0);
+ mPhoneWindowManager.assertLaunchCategory(category);
+ }
+ }
+
+ /**
+ * ALT + TAB to show recent apps.
+ */
+ @Test
+ public void testAltTab() {
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ sendKeyCombination(new int[]{KEYCODE_ALT_LEFT, KEYCODE_TAB}, 0);
+ mPhoneWindowManager.assertShowRecentApps();
+ }
+
+ /**
+ * CTRL + SPACE to switch keyboard layout.
+ */
+ @Test
+ public void testCtrlSpace() {
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0);
+ mPhoneWindowManager.assertSwitchKeyboardLayout();
+ }
+
+ /**
+ * META + SPACE to switch keyboard layout.
+ */
+ @Test
+ public void testMetaSpace() {
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SPACE}, 0);
+ mPhoneWindowManager.assertSwitchKeyboardLayout();
+ }
+
+ /**
+ * CTRL + ALT + Z to enable accessibility service.
+ */
+ @Test
+ public void testCtrlAltZ() {
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_ALT_LEFT, KEYCODE_Z}, 0);
+ mPhoneWindowManager.assertAccessibilityKeychordCalled();
+ }
+
+ /**
+ * META + CTRL+ S to take screenshot.
+ */
+ @Test
+ public void testMetaCtrlS() {
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_S}, 0);
+ mPhoneWindowManager.assertTakeScreenshotCalled();
+ }
+
+ /**
+ * META + N to expand notification panel.
+ */
+ @Test
+ public void testMetaN() throws RemoteException {
+ mPhoneWindowManager.overrideExpandNotificationsPanel();
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0);
+ mPhoneWindowManager.assertExpandNotification();
+ }
+
+ /**
+ * META + SLASH to toggle shortcuts menu.
+ */
+ @Test
+ public void testMetaSlash() {
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SLASH}, 0);
+ mPhoneWindowManager.assertToggleShortcutsMenu();
+ }
+
+ /**
+ * META + ALT to toggle Cap Lock.
+ */
+ @Test
+ public void testMetaAlt() {
+ sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0);
+ mPhoneWindowManager.assertToggleCapsLock();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index ee11ac8..fe46c14 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -32,6 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
@@ -53,6 +54,7 @@
import android.app.NotificationManager;
import android.app.SearchManager;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
@@ -62,6 +64,7 @@
import android.os.HandlerThread;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
+import android.os.RemoteException;
import android.os.Vibrator;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
@@ -73,12 +76,16 @@
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.DisplayPolicy;
import com.android.server.wm.DisplayRotation;
import com.android.server.wm.WindowManagerInternal;
+import junit.framework.Assert;
+
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockSettings;
import org.mockito.Mockito;
@@ -118,6 +125,8 @@
@Mock private GlobalActions mGlobalActions;
@Mock private AccessibilityShortcutController mAccessibilityShortcutController;
+ @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
private StaticMockitoSession mMockitoSession;
private HandlerThread mHandlerThread;
private Handler mHandler;
@@ -226,6 +235,8 @@
mPhoneWindowManager.systemBooted();
overrideLaunchAccessibility();
+ doReturn(false).when(mPhoneWindowManager).keyguardOn();
+ doNothing().when(mContext).startActivityAsUser(any(), any());
}
void tearDown() {
@@ -310,6 +321,22 @@
doReturn(true).when(mTelecomManager).endCall();
}
+ void overrideExpandNotificationsPanel() {
+ // Can't directly mock on IStatusbarService, use spyOn and override the specific api.
+ mPhoneWindowManager.getStatusBarService();
+ spyOn(mPhoneWindowManager.mStatusBarService);
+ try {
+ doNothing().when(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void overrideStatusBarManagerInternal() {
+ doReturn(mStatusBarManagerInternal).when(
+ () -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+ }
+
/**
* Below functions will check the policy behavior could be invoked.
*/
@@ -368,4 +395,46 @@
waitForIdle();
verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
}
+
+ void assertLaunchCategory(String category) {
+ waitForIdle();
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
+ Assert.assertTrue(intentCaptor.getValue().getSelector().hasCategory(category));
+ // Reset verifier for next call.
+ Mockito.reset(mContext);
+ }
+
+ void assertShowRecentApps() {
+ waitForIdle();
+ verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
+ }
+
+ void assertSwitchKeyboardLayout() {
+ waitForIdle();
+ verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), anyInt());
+ }
+
+ void assertTakeBugreport() {
+ waitForIdle();
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
+ any(), anyInt(), any(), any());
+ Assert.assertTrue(intentCaptor.getValue().getAction() == Intent.ACTION_BUG_REPORT);
+ }
+
+ void assertExpandNotification() throws RemoteException {
+ waitForIdle();
+ verify(mPhoneWindowManager.mStatusBarService).expandNotificationsPanel();
+ }
+
+ void assertToggleShortcutsMenu() {
+ waitForIdle();
+ verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
+ }
+
+ void assertToggleCapsLock() {
+ waitForIdle();
+ verify(mInputManagerInternal).toggleCapsLock(anyInt());
+ }
}