Merge "Listener to watch op starts" into rvc-dev am: 316bc6ff66 am: 2d3310ec59
Change-Id: I3c21676964f0317a2aaa25465ba211f68b5bbe2b
This commit is contained in:
@@ -66,6 +66,7 @@ import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
|||||||
import com.android.internal.app.IAppOpsCallback;
|
import com.android.internal.app.IAppOpsCallback;
|
||||||
import com.android.internal.app.IAppOpsNotedCallback;
|
import com.android.internal.app.IAppOpsNotedCallback;
|
||||||
import com.android.internal.app.IAppOpsService;
|
import com.android.internal.app.IAppOpsService;
|
||||||
|
import com.android.internal.app.IAppOpsStartedCallback;
|
||||||
import com.android.internal.app.MessageSamplingConfig;
|
import com.android.internal.app.MessageSamplingConfig;
|
||||||
import com.android.internal.os.RuntimeInit;
|
import com.android.internal.os.RuntimeInit;
|
||||||
import com.android.internal.os.ZygoteInit;
|
import com.android.internal.os.ZygoteInit;
|
||||||
@@ -201,6 +202,10 @@ public class AppOpsManager {
|
|||||||
private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers =
|
private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers =
|
||||||
new ArrayMap<>();
|
new ArrayMap<>();
|
||||||
|
|
||||||
|
@GuardedBy("mStartedWatchers")
|
||||||
|
private final ArrayMap<OnOpStartedListener, IAppOpsStartedCallback> mStartedWatchers =
|
||||||
|
new ArrayMap<>();
|
||||||
|
|
||||||
@GuardedBy("mNotedWatchers")
|
@GuardedBy("mNotedWatchers")
|
||||||
private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
|
private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
|
||||||
new ArrayMap<>();
|
new ArrayMap<>();
|
||||||
@@ -6367,6 +6372,25 @@ public class AppOpsManager {
|
|||||||
default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { }
|
default void onOpActiveChanged(int op, int uid, String packageName, boolean active) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for notification of an op being started.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public interface OnOpStartedListener {
|
||||||
|
/**
|
||||||
|
* Called when an op was started.
|
||||||
|
*
|
||||||
|
* Note: This is only for op starts. It is not called when an op is noted or stopped.
|
||||||
|
*
|
||||||
|
* @param op The op code.
|
||||||
|
* @param uid The UID performing the operation.
|
||||||
|
* @param packageName The package performing the operation.
|
||||||
|
* @param result The result of the start.
|
||||||
|
*/
|
||||||
|
void onOpStarted(int op, int uid, String packageName, int result);
|
||||||
|
}
|
||||||
|
|
||||||
AppOpsManager(Context context, IAppOpsService service) {
|
AppOpsManager(Context context, IAppOpsService service) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mService = service;
|
mService = service;
|
||||||
@@ -6921,6 +6945,73 @@ public class AppOpsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start watching for started app-ops.
|
||||||
|
* An app-op may be long running and it has a clear start delimiter.
|
||||||
|
* If an op start is attempted by any package, you will get a callback.
|
||||||
|
* To change the watched ops for a registered callback you need to unregister and register it
|
||||||
|
* again.
|
||||||
|
*
|
||||||
|
* <p> If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS} permission
|
||||||
|
* you can watch changes only for your UID.
|
||||||
|
*
|
||||||
|
* @param ops The operations to watch.
|
||||||
|
* @param callback Where to report changes.
|
||||||
|
*
|
||||||
|
* @see #stopWatchingStarted(OnOpStartedListener)
|
||||||
|
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
|
||||||
|
* @see #startWatchingNoted(int[], OnOpNotedListener)
|
||||||
|
* @see #startOp(int, int, String, boolean, String, String)
|
||||||
|
* @see #finishOp(int, int, String, String)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
|
||||||
|
public void startWatchingStarted(@NonNull int[] ops, @NonNull OnOpStartedListener callback) {
|
||||||
|
IAppOpsStartedCallback cb;
|
||||||
|
synchronized (mStartedWatchers) {
|
||||||
|
if (mStartedWatchers.containsKey(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb = new IAppOpsStartedCallback.Stub() {
|
||||||
|
@Override
|
||||||
|
public void opStarted(int op, int uid, String packageName, int mode) {
|
||||||
|
callback.onOpStarted(op, uid, packageName, mode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mStartedWatchers.put(callback, cb);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mService.startWatchingStarted(ops, cb);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw e.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop watching for started app-ops.
|
||||||
|
* An app-op may be long running and it has a clear start delimiter.
|
||||||
|
* Henceforth, if an op start is attempted by any package, you will not get a callback.
|
||||||
|
* Unregistering a non-registered callback has no effect.
|
||||||
|
*
|
||||||
|
* @see #startWatchingStarted(int[], OnOpStartedListener)
|
||||||
|
* @see #startOp(int, int, String, boolean, String, String)
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void stopWatchingStarted(@NonNull OnOpStartedListener callback) {
|
||||||
|
synchronized (mStartedWatchers) {
|
||||||
|
final IAppOpsStartedCallback cb = mStartedWatchers.remove(callback);
|
||||||
|
if (cb != null) {
|
||||||
|
try {
|
||||||
|
mService.stopWatchingStarted(cb);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw e.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start watching for noted app ops. An app op may be immediate or long running.
|
* Start watching for noted app ops. An app op may be immediate or long running.
|
||||||
* Immediate ops are noted while long running ones are started and stopped. This
|
* Immediate ops are noted while long running ones are started and stopped. This
|
||||||
@@ -6935,6 +7026,7 @@ public class AppOpsManager {
|
|||||||
* @param callback Where to report changes.
|
* @param callback Where to report changes.
|
||||||
*
|
*
|
||||||
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
|
* @see #startWatchingActive(int[], OnOpActiveChangedListener)
|
||||||
|
* @see #startWatchingStarted(int[], OnOpStartedListener)
|
||||||
* @see #stopWatchingNoted(OnOpNotedListener)
|
* @see #stopWatchingNoted(OnOpNotedListener)
|
||||||
* @see #noteOp(String, int, String, String, String)
|
* @see #noteOp(String, int, String, String, String)
|
||||||
*
|
*
|
||||||
@@ -6974,7 +7066,7 @@ public class AppOpsManager {
|
|||||||
*/
|
*/
|
||||||
public void stopWatchingNoted(@NonNull OnOpNotedListener callback) {
|
public void stopWatchingNoted(@NonNull OnOpNotedListener callback) {
|
||||||
synchronized (mNotedWatchers) {
|
synchronized (mNotedWatchers) {
|
||||||
final IAppOpsNotedCallback cb = mNotedWatchers.get(callback);
|
final IAppOpsNotedCallback cb = mNotedWatchers.remove(callback);
|
||||||
if (cb != null) {
|
if (cb != null) {
|
||||||
try {
|
try {
|
||||||
mService.stopWatchingNoted(cb);
|
mService.stopWatchingNoted(cb);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.android.internal.app.IAppOpsCallback;
|
|||||||
import com.android.internal.app.IAppOpsActiveCallback;
|
import com.android.internal.app.IAppOpsActiveCallback;
|
||||||
import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
||||||
import com.android.internal.app.IAppOpsNotedCallback;
|
import com.android.internal.app.IAppOpsNotedCallback;
|
||||||
|
import com.android.internal.app.IAppOpsStartedCallback;
|
||||||
import com.android.internal.app.MessageSamplingConfig;
|
import com.android.internal.app.MessageSamplingConfig;
|
||||||
|
|
||||||
interface IAppOpsService {
|
interface IAppOpsService {
|
||||||
@@ -91,6 +92,9 @@ interface IAppOpsService {
|
|||||||
void stopWatchingActive(IAppOpsActiveCallback callback);
|
void stopWatchingActive(IAppOpsActiveCallback callback);
|
||||||
boolean isOperationActive(int code, int uid, String packageName);
|
boolean isOperationActive(int code, int uid, String packageName);
|
||||||
|
|
||||||
|
void startWatchingStarted(in int[] ops, IAppOpsStartedCallback callback);
|
||||||
|
void stopWatchingStarted(IAppOpsStartedCallback callback);
|
||||||
|
|
||||||
void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback);
|
void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback);
|
||||||
|
|
||||||
void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
|
void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.internal.app;
|
||||||
|
|
||||||
|
// Iterface to observe op starts
|
||||||
|
oneway interface IAppOpsStartedCallback {
|
||||||
|
void opStarted(int op, int uid, String packageName, int mode);
|
||||||
|
}
|
||||||
@@ -1634,21 +1634,23 @@ public final class ActiveServices {
|
|||||||
new AppOpsManager.OnOpNotedListener() {
|
new AppOpsManager.OnOpNotedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onOpNoted(int op, int uid, String pkgName, int result) {
|
public void onOpNoted(int op, int uid, String pkgName, int result) {
|
||||||
|
incrementOpCountIfNeeded(op, uid, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final AppOpsManager.OnOpStartedListener mOpStartedCallback =
|
||||||
|
new AppOpsManager.OnOpStartedListener() {
|
||||||
|
@Override
|
||||||
|
public void onOpStarted(int op, int uid, String pkgName, int result) {
|
||||||
|
incrementOpCountIfNeeded(op, uid, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void incrementOpCountIfNeeded(int op, int uid, @AppOpsManager.Mode int result) {
|
||||||
if (uid == mProcessRecord.uid && isNotTop()) {
|
if (uid == mProcessRecord.uid && isNotTop()) {
|
||||||
incrementOpCount(op, result == AppOpsManager.MODE_ALLOWED);
|
incrementOpCount(op, result == AppOpsManager.MODE_ALLOWED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private final AppOpsManager.OnOpActiveChangedInternalListener mOpActiveCallback =
|
|
||||||
new AppOpsManager.OnOpActiveChangedInternalListener() {
|
|
||||||
@Override
|
|
||||||
public void onOpActiveChanged(int op, int uid, String pkgName, boolean active) {
|
|
||||||
if (uid == mProcessRecord.uid && active && isNotTop()) {
|
|
||||||
incrementOpCount(op, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private boolean isNotTop() {
|
private boolean isNotTop() {
|
||||||
return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP;
|
return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP;
|
||||||
@@ -1674,7 +1676,7 @@ public final class ActiveServices {
|
|||||||
mNumFgs++;
|
mNumFgs++;
|
||||||
if (mNumFgs == 1) {
|
if (mNumFgs == 1) {
|
||||||
mAppOpsManager.startWatchingNoted(LOGGED_AP_OPS, mOpNotedCallback);
|
mAppOpsManager.startWatchingNoted(LOGGED_AP_OPS, mOpNotedCallback);
|
||||||
mAppOpsManager.startWatchingActive(LOGGED_AP_OPS, mOpActiveCallback);
|
mAppOpsManager.startWatchingStarted(LOGGED_AP_OPS, mOpStartedCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1684,7 +1686,7 @@ public final class ActiveServices {
|
|||||||
mDestroyed = true;
|
mDestroyed = true;
|
||||||
logFinalValues();
|
logFinalValues();
|
||||||
mAppOpsManager.stopWatchingNoted(mOpNotedCallback);
|
mAppOpsManager.stopWatchingNoted(mOpNotedCallback);
|
||||||
mAppOpsManager.stopWatchingActive(mOpActiveCallback);
|
mAppOpsManager.stopWatchingStarted(mOpStartedCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
|||||||
import com.android.internal.app.IAppOpsCallback;
|
import com.android.internal.app.IAppOpsCallback;
|
||||||
import com.android.internal.app.IAppOpsNotedCallback;
|
import com.android.internal.app.IAppOpsNotedCallback;
|
||||||
import com.android.internal.app.IAppOpsService;
|
import com.android.internal.app.IAppOpsService;
|
||||||
|
import com.android.internal.app.IAppOpsStartedCallback;
|
||||||
import com.android.internal.app.MessageSamplingConfig;
|
import com.android.internal.app.MessageSamplingConfig;
|
||||||
import com.android.internal.os.Zygote;
|
import com.android.internal.os.Zygote;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
@@ -1292,6 +1293,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>();
|
final ArrayMap<String, ArraySet<ModeCallback>> mPackageModeWatchers = new ArrayMap<>();
|
||||||
final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
|
final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
|
||||||
final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
|
final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
|
||||||
|
final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
|
||||||
final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
|
final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
|
||||||
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
|
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
|
||||||
|
|
||||||
@@ -1407,6 +1409,50 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class StartedCallback implements DeathRecipient {
|
||||||
|
final IAppOpsStartedCallback mCallback;
|
||||||
|
final int mWatchingUid;
|
||||||
|
final int mCallingUid;
|
||||||
|
final int mCallingPid;
|
||||||
|
|
||||||
|
StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
|
||||||
|
int callingPid) {
|
||||||
|
mCallback = callback;
|
||||||
|
mWatchingUid = watchingUid;
|
||||||
|
mCallingUid = callingUid;
|
||||||
|
mCallingPid = callingPid;
|
||||||
|
try {
|
||||||
|
mCallback.asBinder().linkToDeath(this, 0);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
/*ignored*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder(128);
|
||||||
|
sb.append("StartedCallback{");
|
||||||
|
sb.append(Integer.toHexString(System.identityHashCode(this)));
|
||||||
|
sb.append(" watchinguid=");
|
||||||
|
UserHandle.formatUid(sb, mWatchingUid);
|
||||||
|
sb.append(" from uid=");
|
||||||
|
UserHandle.formatUid(sb, mCallingUid);
|
||||||
|
sb.append(" pid=");
|
||||||
|
sb.append(mCallingPid);
|
||||||
|
sb.append('}');
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() {
|
||||||
|
mCallback.asBinder().unlinkToDeath(this, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void binderDied() {
|
||||||
|
stopWatchingStarted(mCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class NotedCallback implements DeathRecipient {
|
final class NotedCallback implements DeathRecipient {
|
||||||
final IAppOpsNotedCallback mCallback;
|
final IAppOpsNotedCallback mCallback;
|
||||||
final int mWatchingUid;
|
final int mWatchingUid;
|
||||||
@@ -3031,13 +3077,12 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
return AppOpsManager.MODE_ERRORED;
|
return AppOpsManager.MODE_ERRORED;
|
||||||
}
|
}
|
||||||
final Op op = getOpLocked(ops, code, uid, true);
|
final Op op = getOpLocked(ops, code, uid, true);
|
||||||
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
|
|
||||||
if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
|
if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
|
||||||
scheduleOpNotedIfNeededLocked(code, uid, packageName,
|
scheduleOpNotedIfNeededLocked(code, uid, packageName,
|
||||||
AppOpsManager.MODE_IGNORED);
|
AppOpsManager.MODE_IGNORED);
|
||||||
return AppOpsManager.MODE_IGNORED;
|
return AppOpsManager.MODE_IGNORED;
|
||||||
}
|
}
|
||||||
final UidState uidState = ops.uidState;
|
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
|
||||||
if (attributedOp.isRunning()) {
|
if (attributedOp.isRunning()) {
|
||||||
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
|
Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
|
||||||
+ code + " startTime of in progress event="
|
+ code + " startTime of in progress event="
|
||||||
@@ -3045,6 +3090,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final int switchCode = AppOpsManager.opToSwitch(code);
|
final int switchCode = AppOpsManager.opToSwitch(code);
|
||||||
|
final UidState uidState = ops.uidState;
|
||||||
// If there is a non-default per UID policy (we set UID op mode only if
|
// If there is a non-default per UID policy (we set UID op mode only if
|
||||||
// non-default) it takes over, otherwise use the per package policy.
|
// non-default) it takes over, otherwise use the per package policy.
|
||||||
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
|
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
|
||||||
@@ -3076,10 +3122,9 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
+ packageName + (attributionTag == null ? ""
|
+ packageName + (attributionTag == null ? ""
|
||||||
: "." + attributionTag));
|
: "." + attributionTag));
|
||||||
}
|
}
|
||||||
|
scheduleOpNotedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_ALLOWED);
|
||||||
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, uidState.state,
|
attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, uidState.state,
|
||||||
flags);
|
flags);
|
||||||
scheduleOpNotedIfNeededLocked(code, uid, packageName,
|
|
||||||
AppOpsManager.MODE_ALLOWED);
|
|
||||||
|
|
||||||
if (shouldCollectAsyncNotedOp) {
|
if (shouldCollectAsyncNotedOp) {
|
||||||
collectAsyncNotedOp(uid, packageName, code, attributionTag, message);
|
collectAsyncNotedOp(uid, packageName, code, attributionTag, message);
|
||||||
@@ -3092,7 +3137,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
// TODO moltmann: Allow watching for attribution ops
|
// TODO moltmann: Allow watching for attribution ops
|
||||||
@Override
|
@Override
|
||||||
public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
|
public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
|
||||||
int watchedUid = -1;
|
int watchedUid = Process.INVALID_UID;
|
||||||
final int callingUid = Binder.getCallingUid();
|
final int callingUid = Binder.getCallingUid();
|
||||||
final int callingPid = Binder.getCallingPid();
|
final int callingPid = Binder.getCallingPid();
|
||||||
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
|
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
|
||||||
@@ -3138,6 +3183,54 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
|
||||||
|
int watchedUid = Process.INVALID_UID;
|
||||||
|
final int callingUid = Binder.getCallingUid();
|
||||||
|
final int callingPid = Binder.getCallingPid();
|
||||||
|
if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
watchedUid = callingUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
|
||||||
|
Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
|
||||||
|
"Invalid op code in: " + Arrays.toString(ops));
|
||||||
|
Objects.requireNonNull(callback, "Callback cannot be null");
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
|
||||||
|
if (callbacks == null) {
|
||||||
|
callbacks = new SparseArray<>();
|
||||||
|
mStartedWatchers.put(callback.asBinder(), callbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
|
||||||
|
callingUid, callingPid);
|
||||||
|
for (int op : ops) {
|
||||||
|
callbacks.put(op, startedCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopWatchingStarted(IAppOpsStartedCallback callback) {
|
||||||
|
Objects.requireNonNull(callback, "Callback cannot be null");
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
final SparseArray<StartedCallback> startedCallbacks =
|
||||||
|
mStartedWatchers.remove(callback.asBinder());
|
||||||
|
if (startedCallbacks == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int callbackCount = startedCallbacks.size();
|
||||||
|
for (int i = 0; i < callbackCount; i++) {
|
||||||
|
startedCallbacks.valueAt(i).destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
|
public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
|
||||||
int watchedUid = Process.INVALID_UID;
|
int watchedUid = Process.INVALID_UID;
|
||||||
@@ -3340,12 +3433,14 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
final Ops ops = getOpsLocked(uid, resolvedPackageName, attributionTag, bypass,
|
final Ops ops = getOpsLocked(uid, resolvedPackageName, attributionTag, bypass,
|
||||||
true /* edit */);
|
true /* edit */);
|
||||||
if (ops == null) {
|
if (ops == null) {
|
||||||
|
scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_IGNORED);
|
||||||
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
|
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
|
||||||
+ " package " + resolvedPackageName);
|
+ " package " + resolvedPackageName);
|
||||||
return AppOpsManager.MODE_ERRORED;
|
return AppOpsManager.MODE_ERRORED;
|
||||||
}
|
}
|
||||||
final Op op = getOpLocked(ops, code, uid, true);
|
final Op op = getOpLocked(ops, code, uid, true);
|
||||||
if (isOpRestrictedLocked(uid, code, resolvedPackageName, bypass)) {
|
if (isOpRestrictedLocked(uid, code, resolvedPackageName, bypass)) {
|
||||||
|
scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_IGNORED);
|
||||||
return AppOpsManager.MODE_IGNORED;
|
return AppOpsManager.MODE_IGNORED;
|
||||||
}
|
}
|
||||||
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
|
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
|
||||||
@@ -3353,7 +3448,6 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
final UidState uidState = ops.uidState;
|
final UidState uidState = ops.uidState;
|
||||||
// If there is a non-default per UID policy (we set UID op mode only if
|
// If there is a non-default per UID policy (we set UID op mode only if
|
||||||
// non-default) it takes over, otherwise use the per package policy.
|
// non-default) it takes over, otherwise use the per package policy.
|
||||||
final int opCode = op.op;
|
|
||||||
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
|
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
|
||||||
final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
|
final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
|
||||||
if (uidMode != AppOpsManager.MODE_ALLOWED
|
if (uidMode != AppOpsManager.MODE_ALLOWED
|
||||||
@@ -3362,6 +3456,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
+ switchCode + " (" + code + ") uid " + uid + " package "
|
+ switchCode + " (" + code + ") uid " + uid + " package "
|
||||||
+ resolvedPackageName);
|
+ resolvedPackageName);
|
||||||
attributedOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
|
attributedOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
|
||||||
|
scheduleOpStartedIfNeededLocked(code, uid, packageName, uidMode);
|
||||||
return uidMode;
|
return uidMode;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -3374,11 +3469,13 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
+ switchCode + " (" + code + ") uid " + uid + " package "
|
+ switchCode + " (" + code + ") uid " + uid + " package "
|
||||||
+ resolvedPackageName);
|
+ resolvedPackageName);
|
||||||
attributedOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
|
attributedOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
|
||||||
|
scheduleOpStartedIfNeededLocked(code, uid, packageName, mode);
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
|
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
|
||||||
+ " package " + resolvedPackageName);
|
+ " package " + resolvedPackageName);
|
||||||
|
scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_ALLOWED);
|
||||||
try {
|
try {
|
||||||
attributedOp.started(clientId, uidState.state);
|
attributedOp.started(clientId, uidState.state);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
@@ -3480,6 +3577,52 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, int result) {
|
||||||
|
ArraySet<StartedCallback> dispatchedCallbacks = null;
|
||||||
|
final int callbackListCount = mStartedWatchers.size();
|
||||||
|
for (int i = 0; i < callbackListCount; i++) {
|
||||||
|
final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
|
||||||
|
|
||||||
|
StartedCallback callback = callbacks.get(code);
|
||||||
|
if (callback != null) {
|
||||||
|
if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dispatchedCallbacks == null) {
|
||||||
|
dispatchedCallbacks = new ArraySet<>();
|
||||||
|
}
|
||||||
|
dispatchedCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dispatchedCallbacks == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandler.sendMessage(PooledLambda.obtainMessage(
|
||||||
|
AppOpsService::notifyOpStarted,
|
||||||
|
this, dispatchedCallbacks, code, uid, pkgName, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
|
||||||
|
int code, int uid, String packageName, int result) {
|
||||||
|
final long identity = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
final int callbackCount = callbacks.size();
|
||||||
|
for (int i = 0; i < callbackCount; i++) {
|
||||||
|
final StartedCallback callback = callbacks.valueAt(i);
|
||||||
|
try {
|
||||||
|
callback.mCallback.opStarted(code, uid, packageName, result);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Binder.restoreCallingIdentity(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
|
private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
|
||||||
int result) {
|
int result) {
|
||||||
ArraySet<NotedCallback> dispatchedCallbacks = null;
|
ArraySet<NotedCallback> dispatchedCallbacks = null;
|
||||||
@@ -5185,6 +5328,56 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
pw.println(cb);
|
pw.println(cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mStartedWatchers.size() > 0 && dumpMode < 0) {
|
||||||
|
needSep = true;
|
||||||
|
boolean printedHeader = false;
|
||||||
|
|
||||||
|
final int watchersSize = mStartedWatchers.size();
|
||||||
|
for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
|
||||||
|
final SparseArray<StartedCallback> startedWatchers =
|
||||||
|
mStartedWatchers.valueAt(watcherNum);
|
||||||
|
if (startedWatchers.size() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final StartedCallback cb = startedWatchers.valueAt(0);
|
||||||
|
if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dumpPackage != null
|
||||||
|
&& dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!printedHeader) {
|
||||||
|
pw.println(" All op started watchers:");
|
||||||
|
printedHeader = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.print(" ");
|
||||||
|
pw.print(Integer.toHexString(System.identityHashCode(
|
||||||
|
mStartedWatchers.keyAt(watcherNum))));
|
||||||
|
pw.println(" ->");
|
||||||
|
|
||||||
|
pw.print(" [");
|
||||||
|
final int opCount = startedWatchers.size();
|
||||||
|
for (int opNum = 0; opNum < opCount; opNum++) {
|
||||||
|
if (opNum > 0) {
|
||||||
|
pw.print(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
|
||||||
|
if (opNum < opCount - 1) {
|
||||||
|
pw.print(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pw.println("]");
|
||||||
|
|
||||||
|
pw.print(" ");
|
||||||
|
pw.println(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (mNotedWatchers.size() > 0 && dumpMode < 0) {
|
if (mNotedWatchers.size() > 0 && dumpMode < 0) {
|
||||||
needSep = true;
|
needSep = true;
|
||||||
boolean printedHeader = false;
|
boolean printedHeader = false;
|
||||||
|
|||||||
@@ -110,6 +110,22 @@ public class AppOpsActiveWatcherTest {
|
|||||||
|
|
||||||
// We should not be getting any callbacks
|
// We should not be getting any callbacks
|
||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
|
// Start watching op again
|
||||||
|
appOpsManager.startWatchingActive(new String[] {AppOpsManager.OPSTR_CAMERA},
|
||||||
|
getContext().getMainExecutor(), listener);
|
||||||
|
|
||||||
|
// Start the op
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_CAMERA);
|
||||||
|
|
||||||
|
// We should get the callback again (and since we reset the listener, we therefore expect 1)
|
||||||
|
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
|
||||||
|
.times(1)).onOpActiveChanged(eq(AppOpsManager.OPSTR_CAMERA),
|
||||||
|
eq(Process.myUid()), eq(getContext().getPackageName()), eq(true));
|
||||||
|
|
||||||
|
// Finish up
|
||||||
|
appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
|
||||||
|
appOpsManager.stopWatchingActive(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -16,26 +16,26 @@
|
|||||||
|
|
||||||
package com.android.server.appops;
|
package com.android.server.appops;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.AppOpsManager;
|
|
||||||
import android.app.AppOpsManager.OnOpNotedListener;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Process;
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
|
||||||
import androidx.test.filters.SmallTest;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.InOrder;
|
|
||||||
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.timeout;
|
import static org.mockito.Mockito.timeout;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
import android.app.AppOpsManager;
|
||||||
|
import android.app.AppOpsManager.OnOpNotedListener;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Process;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests watching noted ops.
|
* Tests watching noted ops.
|
||||||
*/
|
*/
|
||||||
@@ -77,6 +77,27 @@ public class AppOpsNotedWatcherTest {
|
|||||||
|
|
||||||
// This should be the only two callbacks we got
|
// This should be the only two callbacks we got
|
||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
|
// Note the op again and verify it isn't being watched
|
||||||
|
appOpsManager.noteOp(AppOpsManager.OP_FINE_LOCATION);
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
|
// Start watching again
|
||||||
|
appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
|
||||||
|
AppOpsManager.OP_CAMERA}, listener);
|
||||||
|
|
||||||
|
// Note the op again
|
||||||
|
appOpsManager.noteOp(AppOpsManager.OP_FINE_LOCATION, Process.myUid(),
|
||||||
|
getContext().getPackageName());
|
||||||
|
|
||||||
|
// Verify it's watched again
|
||||||
|
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
|
||||||
|
.times(2)).onOpNoted(eq(AppOpsManager.OP_FINE_LOCATION),
|
||||||
|
eq(Process.myUid()), eq(getContext().getPackageName()),
|
||||||
|
eq(AppOpsManager.MODE_ALLOWED));
|
||||||
|
|
||||||
|
// Finish up
|
||||||
|
appOpsManager.stopWatchingNoted(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Context getContext() {
|
private static Context getContext() {
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.timeout;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
import android.app.AppOpsManager;
|
||||||
|
import android.app.AppOpsManager.OnOpStartedListener;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Process;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
|
/** Tests watching started ops. */
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AppOpsStartedWatcherTest {
|
||||||
|
|
||||||
|
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWatchStartedOps() {
|
||||||
|
// Create a mock listener
|
||||||
|
final OnOpStartedListener listener = mock(OnOpStartedListener.class);
|
||||||
|
|
||||||
|
// Start watching started ops
|
||||||
|
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
|
||||||
|
appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
|
||||||
|
AppOpsManager.OP_CAMERA}, listener);
|
||||||
|
|
||||||
|
// Start some ops
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION);
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_CAMERA);
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_RECORD_AUDIO);
|
||||||
|
|
||||||
|
// Verify that we got called for the ops being started
|
||||||
|
final InOrder inOrder = inOrder(listener);
|
||||||
|
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
|
||||||
|
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
|
||||||
|
eq(Process.myUid()), eq(getContext().getPackageName()),
|
||||||
|
eq(AppOpsManager.MODE_ALLOWED));
|
||||||
|
inOrder.verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
|
||||||
|
.times(1)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
|
||||||
|
eq(Process.myUid()), eq(getContext().getPackageName()),
|
||||||
|
eq(AppOpsManager.MODE_ALLOWED));
|
||||||
|
|
||||||
|
// Stop watching
|
||||||
|
appOpsManager.stopWatchingStarted(listener);
|
||||||
|
|
||||||
|
// This should be the only two callbacks we got
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
|
// Start the op again and verify it isn't being watched
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION);
|
||||||
|
appOpsManager.finishOp(AppOpsManager.OP_FINE_LOCATION);
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
|
// Start watching an op again (only CAMERA this time)
|
||||||
|
appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_CAMERA}, listener);
|
||||||
|
|
||||||
|
// Note the ops again
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_CAMERA);
|
||||||
|
appOpsManager.startOp(AppOpsManager.OP_FINE_LOCATION);
|
||||||
|
|
||||||
|
// Verify it's watched again
|
||||||
|
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
|
||||||
|
.times(2)).onOpStarted(eq(AppOpsManager.OP_CAMERA),
|
||||||
|
eq(Process.myUid()), eq(getContext().getPackageName()),
|
||||||
|
eq(AppOpsManager.MODE_ALLOWED));
|
||||||
|
verifyNoMoreInteractions(listener);
|
||||||
|
|
||||||
|
// Finish up
|
||||||
|
appOpsManager.finishOp(AppOpsManager.OP_CAMERA);
|
||||||
|
appOpsManager.finishOp(AppOpsManager.OP_FINE_LOCATION);
|
||||||
|
appOpsManager.stopWatchingStarted(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Context getContext() {
|
||||||
|
return InstrumentationRegistry.getContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user