blob: 8d472362bc3ed8a0361fba6229a3ecef77fbffc0 [file] [log] [blame]
/*
* Copyright (C) 2012 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 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;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
/**
* API for interacting with "application operation" tracking. Allows you to:
*
* <ul>
* <li> Note when operations are happening, and find out if they are allowed for the current
* caller.</li>
* <li> Disallow specific apps from doing specific operations.</li>
* <li> Collect all of the current information about operations that have been executed or are not
* being allowed.</li>
* <li> Monitor for changes in whether an operation is allowed.</li>
* </ul>
*
* <p>Each operation is identified by a single integer; these integers are a fixed set of
* operations, enumerated by the OP_* constants.
*
* <p></p>When checking operations, the result is a "mode" integer indicating the current setting
* for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute the operation but
* fake its behavior enough so that the caller doesn't crash), MODE_ERRORED (through a
* SecurityException back to the caller; the normal operation calls will do this for you).
*/
public class AppOpsManager {
final Context mContext;
final IAppOpsService mService;
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;
// when adding one of these:
// - increment _NUM_OP
// - add rows to sOpToSwitch, sOpNames, sOpPerms
// - add descriptive strings to Settings/res/values/arrays.xml
/** No operation specified. */
public static final int OP_NONE = -1;
/** Access to coarse location information. */
public static final int OP_COARSE_LOCATION = 0;
/** Access to fine location information. */
public static final int OP_FINE_LOCATION = 1;
/** Causing GPS to run. */
public static final int OP_GPS = 2;
/** @hide */
public static final int OP_VIBRATE = 3;
/** @hide */
public static final int OP_READ_CONTACTS = 4;
/** @hide */
public static final int OP_WRITE_CONTACTS = 5;
/** @hide */
public static final int OP_READ_CALL_LOG = 6;
/** @hide */
public static final int OP_WRITE_CALL_LOG = 7;
/** @hide */
public static final int OP_READ_CALENDAR = 8;
/** @hide */
public static final int OP_WRITE_CALENDAR = 9;
/** @hide */
public static final int OP_WIFI_SCAN = 10;
/** @hide */
public static final int OP_POST_NOTIFICATION = 11;
/** @hide */
public static final int OP_NEIGHBORING_CELLS = 12;
/** @hide */
public static final int OP_CALL_PHONE = 13;
/** @hide */
public static final int OP_READ_SMS = 14;
/** @hide */
public static final int OP_WRITE_SMS = 15;
/** @hide */
public static final int OP_RECEIVE_SMS = 16;
/** @hide */
public static final int OP_RECEIVE_EMERGECY_SMS = 17;
/** @hide */
public static final int OP_RECEIVE_MMS = 18;
/** @hide */
public static final int OP_RECEIVE_WAP_PUSH = 19;
/** @hide */
public static final int OP_SEND_SMS = 20;
/** @hide */
public static final int OP_READ_ICC_SMS = 21;
/** @hide */
public static final int OP_WRITE_ICC_SMS = 22;
/** @hide */
public static final int OP_WRITE_SETTINGS = 23;
/** @hide */
public static final int OP_SYSTEM_ALERT_WINDOW = 24;
/** @hide */
public static final int OP_ACCESS_NOTIFICATIONS = 25;
/** @hide */
public static final int OP_CAMERA = 26;
/** @hide */
public static final int OP_RECORD_AUDIO = 27;
/** @hide */
public static final int OP_PLAY_AUDIO = 28;
/** @hide */
public static final int OP_READ_CLIPBOARD = 29;
/** @hide */
public static final int OP_WRITE_CLIPBOARD = 30;
/** @hide */
public static final int OP_TAKE_MEDIA_BUTTONS = 31;
/** @hide */
public static final int OP_TAKE_AUDIO_FOCUS = 32;
/** @hide */
public static final int OP_AUDIO_MASTER_VOLUME = 33;
/** @hide */
public static final int OP_AUDIO_VOICE_VOLUME = 34;
/** @hide */
public static final int OP_AUDIO_RING_VOLUME = 35;
/** @hide */
public static final int OP_AUDIO_MEDIA_VOLUME = 36;
/** @hide */
public static final int OP_AUDIO_ALARM_VOLUME = 37;
/** @hide */
public static final int OP_AUDIO_NOTIFICATION_VOLUME = 38;
/** @hide */
public static final int OP_AUDIO_BLUETOOTH_VOLUME = 39;
/** @hide */
public static final int OP_WAKE_LOCK = 40;
/** Continually monitoring location data. */
public static final int OP_MONITOR_LOCATION = 41;
/** @hide */
public static final int _NUM_OP = 42;
/**
* This maps each operation to the operation that serves as the
* switch to determine whether it is allowed. Generally this is
* a 1:1 mapping, but for some things (like location) that have
* multiple low-level operations being tracked that should be
* presented to hte user as one switch then this can be used to
* make them all controlled by the same single operation.
*/
private static int[] sOpToSwitch = new int[] {
OP_COARSE_LOCATION,
OP_COARSE_LOCATION,
OP_COARSE_LOCATION,
OP_VIBRATE,
OP_READ_CONTACTS,
OP_WRITE_CONTACTS,
OP_READ_CALL_LOG,
OP_WRITE_CALL_LOG,
OP_READ_CALENDAR,
OP_WRITE_CALENDAR,
OP_COARSE_LOCATION,
OP_POST_NOTIFICATION,
OP_COARSE_LOCATION,
OP_CALL_PHONE,
OP_READ_SMS,
OP_WRITE_SMS,
OP_READ_SMS,
OP_READ_SMS,
OP_READ_SMS,
OP_READ_SMS,
OP_WRITE_SMS,
OP_READ_SMS,
OP_WRITE_SMS,
OP_WRITE_SETTINGS,
OP_SYSTEM_ALERT_WINDOW,
OP_ACCESS_NOTIFICATIONS,
OP_CAMERA,
OP_RECORD_AUDIO,
OP_PLAY_AUDIO,
OP_READ_CLIPBOARD,
OP_WRITE_CLIPBOARD,
OP_TAKE_MEDIA_BUTTONS,
OP_TAKE_AUDIO_FOCUS,
OP_AUDIO_MASTER_VOLUME,
OP_AUDIO_VOICE_VOLUME,
OP_AUDIO_RING_VOLUME,
OP_AUDIO_MEDIA_VOLUME,
OP_AUDIO_ALARM_VOLUME,
OP_AUDIO_NOTIFICATION_VOLUME,
OP_AUDIO_BLUETOOTH_VOLUME,
OP_WAKE_LOCK,
OP_COARSE_LOCATION,
};
/**
* This provides a simple name for each operation to be used
* in debug output.
*/
private static String[] sOpNames = new String[] {
"COARSE_LOCATION",
"FINE_LOCATION",
"GPS",
"VIBRATE",
"READ_CONTACTS",
"WRITE_CONTACTS",
"READ_CALL_LOG",
"WRITE_CALL_LOG",
"READ_CALENDAR",
"WRITE_CALENDAR",
"WIFI_SCAN",
"POST_NOTIFICATION",
"NEIGHBORING_CELLS",
"CALL_PHONE",
"READ_SMS",
"WRITE_SMS",
"RECEIVE_SMS",
"RECEIVE_EMERGECY_SMS",
"RECEIVE_MMS",
"RECEIVE_WAP_PUSH",
"SEND_SMS",
"READ_ICC_SMS",
"WRITE_ICC_SMS",
"WRITE_SETTINGS",
"SYSTEM_ALERT_WINDOW",
"ACCESS_NOTIFICATIONS",
"CAMERA",
"RECORD_AUDIO",
"PLAY_AUDIO",
"READ_CLIPBOARD",
"WRITE_CLIPBOARD",
"TAKE_MEDIA_BUTTONS",
"TAKE_AUDIO_FOCUS",
"AUDIO_MASTER_VOLUME",
"AUDIO_VOICE_VOLUME",
"AUDIO_RING_VOLUME",
"AUDIO_MEDIA_VOLUME",
"AUDIO_ALARM_VOLUME",
"AUDIO_NOTIFICATION_VOLUME",
"AUDIO_BLUETOOTH_VOLUME",
"WAKE_LOCK",
"MONITOR_LOCATION",
};
/**
* This optionally maps a permission to an operation. If there
* is no permission associated with an operation, it is null.
*/
private static String[] sOpPerms = new String[] {
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION,
null,
android.Manifest.permission.VIBRATE,
android.Manifest.permission.READ_CONTACTS,
android.Manifest.permission.WRITE_CONTACTS,
android.Manifest.permission.READ_CALL_LOG,
android.Manifest.permission.WRITE_CALL_LOG,
android.Manifest.permission.READ_CALENDAR,
android.Manifest.permission.WRITE_CALENDAR,
null, // no permission required for notifications
android.Manifest.permission.ACCESS_WIFI_STATE,
null, // neighboring cells shares the coarse location perm
android.Manifest.permission.CALL_PHONE,
android.Manifest.permission.READ_SMS,
android.Manifest.permission.WRITE_SMS,
android.Manifest.permission.RECEIVE_SMS,
android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
android.Manifest.permission.RECEIVE_MMS,
android.Manifest.permission.RECEIVE_WAP_PUSH,
android.Manifest.permission.SEND_SMS,
android.Manifest.permission.READ_SMS,
android.Manifest.permission.WRITE_SMS,
android.Manifest.permission.WRITE_SETTINGS,
android.Manifest.permission.SYSTEM_ALERT_WINDOW,
android.Manifest.permission.ACCESS_NOTIFICATIONS,
android.Manifest.permission.CAMERA,
android.Manifest.permission.RECORD_AUDIO,
null, // no permission for playing audio
null, // no permission for reading clipboard
null, // no permission for writing clipboard
null, // no permission for taking media buttons
null, // no permission for taking audio focus
null, // no permission for changing master volume
null, // no permission for changing voice volume
null, // no permission for changing ring volume
null, // no permission for changing media volume
null, // no permission for changing alarm volume
null, // no permission for changing notification volume
null, // no permission for changing bluetooth volume
android.Manifest.permission.WAKE_LOCK,
null, // no permission for generic location monitoring
};
/**
* Retrieve the op switch that controls the given operation.
* @hide
*/
public static int opToSwitch(int op) {
return sOpToSwitch[op];
}
/**
* Retrieve a non-localized name for the operation, for debugging output.
*/
public static String opToName(int op) {
if (op == OP_NONE) return "NONE";
return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
}
/**
* Retrieve the permission associated with an operation, or null if there is not one.
* @hide
*/
public static String opToPermission(int op) {
return sOpPerms[op];
}
/**
* Class holding all of the operation information associated with an app.
* @hide
*/
public static class PackageOps implements Parcelable {
private final String mPackageName;
private final int mUid;
private final List<OpEntry> mEntries;
public PackageOps(String packageName, int uid, List<OpEntry> entries) {
mPackageName = packageName;
mUid = uid;
mEntries = entries;
}
public String getPackageName() {
return mPackageName;
}
public int getUid() {
return mUid;
}
public List<OpEntry> getOps() {
return mEntries;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mPackageName);
dest.writeInt(mUid);
dest.writeInt(mEntries.size());
for (int i=0; i<mEntries.size(); i++) {
mEntries.get(i).writeToParcel(dest, flags);
}
}
PackageOps(Parcel source) {
mPackageName = source.readString();
mUid = source.readInt();
mEntries = new ArrayList<OpEntry>();
final int N = source.readInt();
for (int i=0; i<N; i++) {
mEntries.add(OpEntry.CREATOR.createFromParcel(source));
}
}
public static final Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
@Override public PackageOps createFromParcel(Parcel source) {
return new PackageOps(source);
}
@Override public PackageOps[] newArray(int size) {
return new PackageOps[size];
}
};
}
/**
* Class holding the information about one unique operation of an application.
* @hide
*/
public static class OpEntry implements Parcelable {
private final int mOp;
private final int mMode;
private final long mTime;
private final long mRejectTime;
private final int mDuration;
public OpEntry(int op, int mode, long time, long rejectTime, int duration) {
mOp = op;
mMode = mode;
mTime = time;
mRejectTime = rejectTime;
mDuration = duration;
}
public int getOp() {
return mOp;
}
public int getMode() {
return mMode;
}
public long getTime() {
return mTime;
}
public long getRejectTime() {
return mRejectTime;
}
public boolean isRunning() {
return mDuration == -1;
}
public int getDuration() {
return mDuration == -1 ? (int)(System.currentTimeMillis()-mTime) : mDuration;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mOp);
dest.writeInt(mMode);
dest.writeLong(mTime);
dest.writeLong(mRejectTime);
dest.writeInt(mDuration);
}
OpEntry(Parcel source) {
mOp = source.readInt();
mMode = source.readInt();
mTime = source.readLong();
mRejectTime = source.readLong();
mDuration = source.readInt();
}
public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
@Override public OpEntry createFromParcel(Parcel source) {
return new OpEntry(source);
}
@Override public OpEntry[] newArray(int size) {
return new OpEntry[size];
}
};
}
/**
* Callback for notification of changes to operation state.
*/
public interface Callback {
public void opChanged(int op, String packageName);
}
AppOpsManager(Context context, IAppOpsService service) {
mContext = context;
mService = service;
}
/**
* Retrieve current operation state for all applications.
*
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
*/
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
try {
return mService.getPackagesForOps(ops);
} catch (RemoteException e) {
}
return null;
}
/**
* Retrieve current operation state for one application.
*
* @param uid The uid of the application of interest.
* @param packageName The name of the application of interest.
* @param ops The set of operations you are interested in, or null if you want all of them.
* @hide
*/
public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
try {
return mService.getOpsForPackage(uid, packageName, ops);
} catch (RemoteException e) {
}
return null;
}
/** @hide */
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
}
}
/**
* Monitor for changes to the operating mode for the given op in the given app package.
* @param op The operation to monitor, one of OP_*.
* @param packageName The name of the application to monitor.
* @param callback Where to report changes.
*/
public void startWatchingMode(int op, String packageName, final Callback callback) {
synchronized (mModeWatchers) {
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb == null) {
cb = new IAppOpsCallback.Stub() {
public void opChanged(int op, String packageName) {
callback.opChanged(op, packageName);
}
};
mModeWatchers.put(callback, cb);
}
try {
mService.startWatchingMode(op, packageName, cb);
} catch (RemoteException e) {
}
}
}
/**
* Stop monitoring that was previously started with {@link #startWatchingMode}. All
* monitoring associated with this callback will be removed.
*/
public void stopWatchingMode(Callback callback) {
synchronized (mModeWatchers) {
IAppOpsCallback cb = mModeWatchers.get(callback);
if (cb != null) {
try {
mService.stopWatchingMode(cb);
} catch (RemoteException e) {
}
}
}
}
/**
* Do a quick check for whether an application might be able to perform an operation.
* This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
* or {@link #startOp(int, int, String)} for your actual security checks, which also
* ensure that the given uid and package name are consistent. This function can just be
* used for a quick check to see if an operation has been disabled for the application,
* as an early reject of some work. This does not modify the time stamp or other data
* about the operation.
* @param op The operation to check. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
* @throws SecurityException If the app has been configured to crash on this op.
*/
public int checkOp(int op, int uid, String packageName) {
try {
int mode = mService.checkOperation(op, uid, packageName);
if (mode == MODE_ERRORED) {
throw new SecurityException("Operation not allowed");
}
return mode;
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
/**
* Like {@link #checkOp} but instead of throwing a {@link SecurityException} it
* returns {@link #MODE_ERRORED}.
*/
public int checkOpNoThrow(int op, int uid, String packageName) {
try {
return mService.checkOperation(op, uid, packageName);
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
/**
* Make note of an application performing an operation. Note that you must pass
* in both the uid and name of the application to be checked; this function will verify
* that these two match, and if not, return {@link #MODE_IGNORED}. If this call
* succeeds, the last execution time of the operation for this app will be updated to
* the current time.
* @param op The operation to note. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
* @throws SecurityException If the app has been configured to crash on this op.
*/
public int noteOp(int op, int uid, String packageName) {
try {
int mode = mService.noteOperation(op, uid, packageName);
if (mode == MODE_ERRORED) {
throw new SecurityException("Operation not allowed");
}
return mode;
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
/**
* Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
* returns {@link #MODE_ERRORED}.
*/
public int noteOpNoThrow(int op, int uid, String packageName) {
try {
return mService.noteOperation(op, uid, packageName);
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
/** @hide */
public int noteOp(int op) {
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
* verify that these two match, and if not, return {@link #MODE_IGNORED}. If this call
* succeeds, the last execution time of the operation for this app will be updated to
* the current time and the operation will be marked as "running". In this case you must
* later call {@link #finishOp(int, int, String)} to report when the application is no
* longer performing the operation.
* @param op The operation to start. One of the OP_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
* causing the app to crash).
* @throws SecurityException If the app has been configured to crash on this op.
*/
public int startOp(int op, int uid, String packageName) {
try {
int mode = mService.startOperation(getToken(mService), op, uid, packageName);
if (mode == MODE_ERRORED) {
throw new SecurityException("Operation not allowed");
}
return mode;
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
/**
* Like {@link #startOp} but instead of throwing a {@link SecurityException} it
* returns {@link #MODE_ERRORED}.
*/
public int startOpNoThrow(int op, int uid, String packageName) {
try {
return mService.startOperation(getToken(mService), op, uid, packageName);
} catch (RemoteException e) {
}
return MODE_IGNORED;
}
/** @hide */
public int startOp(int op) {
return startOp(op, Process.myUid(), mContext.getBasePackageName());
}
/**
* Report that an application is no longer performing an operation that had previously
* been started with {@link #startOp(int, int, String)}. There is no validation of input
* or result; the parameters supplied here must be the exact same ones previously passed
* in when starting the operation.
*/
public void finishOp(int op, int uid, String packageName) {
try {
mService.finishOperation(getToken(mService), op, uid, packageName);
} catch (RemoteException e) {
}
}
public void finishOp(int op) {
finishOp(op, Process.myUid(), mContext.getBasePackageName());
}
}