Make it safe to use start/stop app ops outside of system proc

We now keep track of all of the active start operations per
non-system process, so they can be cleaned up if the process
goes away.

Change-Id: I9d05f1e0281c47dbe1213de014f0491f1359685c
This commit is contained in:
Dianne Hackborn
2013-07-17 17:23:25 -07:00
parent 209bede6b9
commit e98f5dbe6b
6 changed files with 192 additions and 40 deletions

View File

@@ -16,6 +16,8 @@
package android.app;
import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsCallback;
@@ -55,6 +57,8 @@ public class AppOpsManager {
final ArrayMap<Callback, IAppOpsCallback> mModeWatchers
= new ArrayMap<Callback, IAppOpsCallback>();
static IBinder sToken;
public static final int MODE_ALLOWED = 0;
public static final int MODE_IGNORED = 1;
public static final int MODE_ERRORED = 2;
@@ -640,6 +644,21 @@ public class AppOpsManager {
return noteOp(op, Process.myUid(), mContext.getBasePackageName());
}
/** @hide */
public static IBinder getToken(IAppOpsService service) {
synchronized (AppOpsManager.class) {
if (sToken != null) {
return sToken;
}
try {
sToken = service.getToken(new Binder());
} catch (RemoteException e) {
// System is dead, whatevs.
}
return sToken;
}
}
/**
* Report that an application has started executing a long-running operation. Note that you
* must pass in both the uid and name of the application to be checked; this function will
@@ -658,7 +677,7 @@ public class AppOpsManager {
*/
public int startOp(int op, int uid, String packageName) {
try {
int mode = mService.startOperation(op, uid, packageName);
int mode = mService.startOperation(getToken(mService), op, uid, packageName);
if (mode == MODE_ERRORED) {
throw new SecurityException("Operation not allowed");
}
@@ -674,7 +693,7 @@ public class AppOpsManager {
*/
public int startOpNoThrow(int op, int uid, String packageName) {
try {
return mService.startOperation(op, uid, packageName);
return mService.startOperation(getToken(mService), op, uid, packageName);
} catch (RemoteException e) {
}
return MODE_IGNORED;
@@ -693,7 +712,7 @@ public class AppOpsManager {
*/
public void finishOp(int op, int uid, String packageName) {
try {
mService.finishOperation(op, uid, packageName);
mService.finishOperation(getToken(mService), op, uid, packageName);
} catch (RemoteException e) {
}
}

View File

@@ -24,10 +24,11 @@ interface IAppOpsService {
// be kept in sync with frameworks/native/include/binder/IAppOpsService.h
int checkOperation(int code, int uid, String packageName);
int noteOperation(int code, int uid, String packageName);
int startOperation(int code, int uid, String packageName);
void finishOperation(int code, int uid, String packageName);
int startOperation(IBinder token, int code, int uid, String packageName);
void finishOperation(IBinder token, int code, int uid, String packageName);
void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
void stopWatchingMode(IAppOpsCallback callback);
IBinder getToken(IBinder clientToken);
// Remaining methods are only used in Java.
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);

View File

@@ -40,6 +40,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
@@ -97,6 +98,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
public final static class Op {
public final int uid;
public final String packageName;
public final int op;
public int mode;
public int duration;
@@ -104,7 +107,9 @@ public class AppOpsService extends IAppOpsService.Stub {
public long rejectTime;
public int nesting;
public Op(int _op) {
public Op(int _uid, String _packageName, int _op) {
uid = _uid;
packageName = _packageName;
op = _op;
mode = AppOpsManager.MODE_ALLOWED;
}
@@ -112,10 +117,10 @@ public class AppOpsService extends IAppOpsService.Stub {
final SparseArray<ArrayList<Callback>> mOpModeWatchers
= new SparseArray<ArrayList<Callback>>();
final HashMap<String, ArrayList<Callback>> mPackageModeWatchers
= new HashMap<String, ArrayList<Callback>>();
final HashMap<IBinder, Callback> mModeWatchers
= new HashMap<IBinder, Callback>();
final ArrayMap<String, ArrayList<Callback>> mPackageModeWatchers
= new ArrayMap<String, ArrayList<Callback>>();
final ArrayMap<IBinder, Callback> mModeWatchers
= new ArrayMap<IBinder, Callback>();
public final class Callback implements DeathRecipient {
final IAppOpsCallback mCallback;
@@ -138,6 +143,47 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<IBinder, ClientState>();
public final class ClientState extends Binder implements DeathRecipient {
final IBinder mAppToken;
final int mPid;
final ArrayList<Op> mStartedOps;
public ClientState(IBinder appToken) {
mAppToken = appToken;
mPid = Binder.getCallingPid();
if (appToken instanceof Binder) {
// For local clients, there is no reason to track them.
mStartedOps = null;
} else {
mStartedOps = new ArrayList<Op>();
try {
mAppToken.linkToDeath(this, 0);
} catch (RemoteException e) {
}
}
}
@Override
public String toString() {
return "ClientState{" +
"mAppToken=" + mAppToken +
", " + (mStartedOps != null ? ("pid=" + mPid) : "local") +
'}';
}
@Override
public void binderDied() {
synchronized (AppOpsService.this) {
for (int i=mStartedOps.size()-1; i>=0; i--) {
finishOperationLocked(mStartedOps.get(i));
}
mClients.remove(mAppToken);
}
}
}
public AppOpsService(File storagePath) {
mFile = new AtomicFile(storagePath);
mHandler = new Handler();
@@ -380,27 +426,36 @@ public class AppOpsService extends IAppOpsService.Stub {
Callback cb = mModeWatchers.remove(callback.asBinder());
if (cb != null) {
cb.unlinkToDeath();
for (int i=0; i<mOpModeWatchers.size(); i++) {
for (int i=mOpModeWatchers.size()-1; i>=0; i--) {
ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
cbs.remove(cb);
if (cbs.size() <= 0) {
mOpModeWatchers.removeAt(i);
}
}
if (mPackageModeWatchers.size() > 0) {
Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator();
while (it.hasNext()) {
ArrayList<Callback> cbs = it.next();
cbs.remove(cb);
if (cbs.size() <= 0) {
it.remove();
}
for (int i=mPackageModeWatchers.size()-1; i>=0; i--) {
ArrayList<Callback> cbs = mPackageModeWatchers.valueAt(i);
cbs.remove(cb);
if (cbs.size() <= 0) {
mPackageModeWatchers.removeAt(i);
}
}
}
}
}
@Override
public IBinder getToken(IBinder clientToken) {
synchronized (this) {
ClientState cs = mClients.get(clientToken);
if (cs == null) {
cs = new ClientState(clientToken);
mClients.put(clientToken, cs);
}
return cs;
}
}
@Override
public int checkOperation(int code, int uid, String packageName) {
verifyIncomingUid(uid);
@@ -448,9 +503,10 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@Override
public int startOperation(int code, int uid, String packageName) {
public int startOperation(IBinder token, int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
ClientState client = (ClientState)token;
synchronized (this) {
Ops ops = getOpsLocked(uid, packageName, true);
if (ops == null) {
@@ -475,32 +531,46 @@ public class AppOpsService extends IAppOpsService.Stub {
op.duration = -1;
}
op.nesting++;
if (client.mStartedOps != null) {
client.mStartedOps.add(op);
}
return AppOpsManager.MODE_ALLOWED;
}
}
@Override
public void finishOperation(int code, int uid, String packageName) {
public void finishOperation(IBinder token, int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
ClientState client = (ClientState)token;
synchronized (this) {
Op op = getOpLocked(code, uid, packageName, true);
if (op == null) {
return;
}
if (op.nesting <= 1) {
if (op.nesting == 1) {
op.duration = (int)(System.currentTimeMillis() - op.time);
op.time += op.duration;
} else {
Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName
+ " code " + code + " time=" + op.time + " duration=" + op.duration
+ " nesting=" + op.nesting);
if (client.mStartedOps != null) {
if (!client.mStartedOps.remove(op)) {
throw new IllegalStateException("Operation not started: uid" + op.uid
+ " pkg=" + op.packageName + " op=" + op.op);
}
op.nesting = 0;
} else {
op.nesting--;
}
finishOperationLocked(op);
}
}
void finishOperationLocked(Op op) {
if (op.nesting <= 1) {
if (op.nesting == 1) {
op.duration = (int)(System.currentTimeMillis() - op.time);
op.time += op.duration;
} else {
Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg "
+ op.packageName + " code " + op.op + " time=" + op.time
+ " duration=" + op.duration + " nesting=" + op.nesting);
}
op.nesting = 0;
} else {
op.nesting--;
}
}
@@ -601,7 +671,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!edit) {
return null;
}
op = new Op(code);
op = new Op(ops.uid, ops.packageName, code);
ops.put(code, op);
}
if (edit) {
@@ -711,7 +781,7 @@ public class AppOpsService extends IAppOpsService.Stub {
String tagName = parser.getName();
if (tagName.equals("op")) {
Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
String mode = parser.getAttributeValue(null, "m");
if (mode != null) {
op.mode = Integer.parseInt(mode);
@@ -831,6 +901,62 @@ public class AppOpsService extends IAppOpsService.Stub {
synchronized (this) {
pw.println("Current AppOps Service state:");
final long now = System.currentTimeMillis();
boolean needSep = false;
if (mOpModeWatchers.size() > 0) {
needSep = true;
pw.println(" Op mode watchers:");
for (int i=0; i<mOpModeWatchers.size(); i++) {
pw.print(" Op "); pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
pw.println(":");
ArrayList<Callback> callbacks = mOpModeWatchers.valueAt(i);
for (int j=0; j<callbacks.size(); j++) {
pw.print(" #"); pw.print(j); pw.print(": ");
pw.println(callbacks.get(j));
}
}
}
if (mPackageModeWatchers.size() > 0) {
needSep = true;
pw.println(" Package mode watchers:");
for (int i=0; i<mPackageModeWatchers.size(); i++) {
pw.print(" Pkg "); pw.print(mPackageModeWatchers.keyAt(i));
pw.println(":");
ArrayList<Callback> callbacks = mPackageModeWatchers.valueAt(i);
for (int j=0; j<callbacks.size(); j++) {
pw.print(" #"); pw.print(j); pw.print(": ");
pw.println(callbacks.get(j));
}
}
}
if (mModeWatchers.size() > 0) {
needSep = true;
pw.println(" All mode watchers:");
for (int i=0; i<mModeWatchers.size(); i++) {
pw.print(" "); pw.print(mModeWatchers.keyAt(i));
pw.print(" -> "); pw.println(mModeWatchers.valueAt(i));
}
}
if (mClients.size() > 0) {
needSep = true;
pw.println(" Clients:");
for (int i=0; i<mClients.size(); i++) {
pw.print(" "); pw.print(mClients.keyAt(i)); pw.println(":");
ClientState cs = mClients.valueAt(i);
pw.print(" "); pw.println(cs);
if (cs.mStartedOps != null && cs.mStartedOps.size() > 0) {
pw.println(" Started ops:");
for (int j=0; j<cs.mStartedOps.size(); j++) {
Op op = cs.mStartedOps.get(j);
pw.print(" "); pw.print("uid="); pw.print(op.uid);
pw.print(" pkg="); pw.print(op.packageName);
pw.print(" op="); pw.println(AppOpsManager.opToName(op.op));
}
}
}
}
if (needSep) {
pw.println();
}
for (int i=0; i<mUidOps.size(); i++) {
pw.print(" Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);

View File

@@ -342,7 +342,8 @@ public class VibratorService extends IVibratorService.Stub
// Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
try {
int mode = mAppOpsService.startOperation(AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
int mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
if (mode == AppOpsManager.MODE_ERRORED) {
Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
@@ -366,7 +367,8 @@ public class VibratorService extends IVibratorService.Stub
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
mAppOpsService.finishOperation(AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mCurrentVibration.mPackageName);
} catch (RemoteException e) {
}

View File

@@ -892,7 +892,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
for (int i=0; i<newWork.size(); i++) {
try {
int uid = newWork.get(i);
mAppOpsService.startOperation(AppOpsManager.OP_GPS, uid, newWork.getName(i));
mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_GPS, uid, newWork.getName(i));
if (uid != lastuid) {
lastuid = uid;
mBatteryStats.noteStartGps(uid);
@@ -909,7 +910,8 @@ public class GpsLocationProvider implements LocationProviderInterface {
for (int i=0; i<goneWork.size(); i++) {
try {
int uid = goneWork.get(i);
mAppOpsService.finishOperation(AppOpsManager.OP_GPS, uid, goneWork.getName(i));
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_GPS, uid, goneWork.getName(i));
if (uid != lastuid) {
lastuid = uid;
mBatteryStats.noteStopGps(uid);

View File

@@ -144,7 +144,8 @@ final class Notifier {
} else {
mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType);
// XXX need to deal with disabled operations.
mAppOps.startOperation(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
mAppOps.startOperation(AppOpsManager.getToken(mAppOps),
AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
} catch (RemoteException ex) {
// Ignore
@@ -169,7 +170,8 @@ final class Notifier {
mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType);
} else {
mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType);
mAppOps.finishOperation(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
mAppOps.finishOperation(AppOpsManager.getToken(mAppOps),
AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
} catch (RemoteException ex) {
// Ignore