App ops: you can now turn off operations.

Also add new ops for calendar and wi-fi scans, finish
implementing rejection of content provider calls, fix
issues with rejecting location calls, fix bug in the
new pm call to retrieve apps with permissions.

Change-Id: I29d9f8600bfbbf6561abf6d491907e2bbf6af417
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
index 3d9ddae..bf2a5ae 100644
--- a/services/java/com/android/server/AppOpsService.java
+++ b/services/java/com/android/server/AppOpsService.java
@@ -38,6 +38,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.AtomicFile;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -93,12 +94,15 @@
 
     final static class Op {
         public final int op;
+        public int mode;
         public int duration;
         public long time;
+        public long rejectTime;
         public int nesting;
 
         public Op(int _op) {
             op = _op;
+            mode = AppOpsManager.MODE_ALLOWED;
         }
     }
 
@@ -133,8 +137,8 @@
             resOps = new ArrayList<AppOpsManager.OpEntry>();
             for (int j=0; j<pkgOps.size(); j++) {
                 Op curOp = pkgOps.valueAt(j);
-                resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
-                        curOp.duration));
+                resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
+                        curOp.rejectTime, curOp.duration));
             }
         } else {
             for (int j=0; j<ops.length; j++) {
@@ -143,8 +147,8 @@
                     if (resOps == null) {
                         resOps = new ArrayList<AppOpsManager.OpEntry>();
                     }
-                    resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.time,
-                            curOp.duration));
+                    resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
+                            curOp.rejectTime, curOp.duration));
                 }
             }
         }
@@ -198,6 +202,20 @@
     }
 
     @Override
+    public void setMode(int code, int uid, String packageName, int mode) {
+        uid = handleIncomingUid(uid);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, true);
+            if (op != null) {
+                if (op.mode != mode) {
+                    op.mode = mode;
+                    scheduleWriteNowLocked();
+                }
+            }
+        }
+    }
+
+    @Override
     public int checkOperation(int code, int uid, String packageName) {
         uid = handleIncomingUid(uid);
         synchronized (this) {
@@ -205,8 +223,8 @@
             if (op == null) {
                 return AppOpsManager.MODE_ALLOWED;
             }
+            return op.mode;
         }
-        return AppOpsManager.MODE_ALLOWED;
     }
 
     @Override
@@ -215,16 +233,26 @@
         synchronized (this) {
             Op op = getOpLocked(code, uid, packageName, true);
             if (op == null) {
+                if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+                        + " package " + packageName);
                 return AppOpsManager.MODE_IGNORED;
             }
             if (op.duration == -1) {
                 Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
                         + " code " + code + " time=" + op.time + " duration=" + op.duration);
             }
-            op.time = System.currentTimeMillis();
             op.duration = 0;
+            if (op.mode != AppOpsManager.MODE_ALLOWED) {
+                if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code " + code
+                        + " uid " + uid + " package " + packageName);
+                op.rejectTime = System.currentTimeMillis();
+                return op.mode;
+            }
+            if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+                    + " package " + packageName);
+            op.time = System.currentTimeMillis();
+            return AppOpsManager.MODE_ALLOWED;
         }
-        return AppOpsManager.MODE_ALLOWED;
     }
 
     @Override
@@ -233,15 +261,25 @@
         synchronized (this) {
             Op op = getOpLocked(code, uid, packageName, true);
             if (op == null) {
+                if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                        + " package " + packageName);
                 return AppOpsManager.MODE_IGNORED;
             }
+            if (op.mode != AppOpsManager.MODE_ALLOWED) {
+                if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code " + code
+                        + " uid " + uid + " package " + packageName);
+                op.rejectTime = System.currentTimeMillis();
+                return op.mode;
+            }
+            if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                    + " package " + packageName);
             if (op.nesting == 0) {
                 op.time = System.currentTimeMillis();
                 op.duration = -1;
             }
             op.nesting++;
+            return AppOpsManager.MODE_ALLOWED;
         }
-        return AppOpsManager.MODE_ALLOWED;
     }
 
     @Override
@@ -319,6 +357,21 @@
         return ops;
     }
 
+    private void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    private void scheduleWriteNowLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+        }
+        mHandler.removeCallbacks(mWriteRunner);
+        mHandler.post(mWriteRunner);
+    }
+
     private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
         Ops ops = getOpsLocked(uid, packageName, edit);
         if (ops == null) {
@@ -332,9 +385,8 @@
             op = new Op(code);
             ops.put(code, op);
         }
-        if (edit && !mWriteScheduled) {
-            mWriteScheduled = true;
-            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        if (edit) {
+            scheduleWriteLocked();
         }
         return op;
     }
@@ -441,8 +493,22 @@
             String tagName = parser.getName();
             if (tagName.equals("op")) {
                 Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n")));
-                op.time = Long.parseLong(parser.getAttributeValue(null, "t"));
-                op.duration = Integer.parseInt(parser.getAttributeValue(null, "d"));
+                String mode = parser.getAttributeValue(null, "m");
+                if (mode != null) {
+                    op.mode = Integer.parseInt(mode);
+                }
+                String time = parser.getAttributeValue(null, "t");
+                if (time != null) {
+                    op.time = Long.parseLong(time);
+                }
+                time = parser.getAttributeValue(null, "r");
+                if (time != null) {
+                    op.rejectTime = Long.parseLong(time);
+                }
+                String dur = parser.getAttributeValue(null, "d");
+                if (dur != null) {
+                    op.duration = Integer.parseInt(dur);
+                }
                 HashMap<String, Ops> pkgOps = mUidOps.get(uid);
                 if (pkgOps == null) {
                     pkgOps = new HashMap<String, Ops>();
@@ -499,8 +565,21 @@
                             AppOpsManager.OpEntry op = ops.get(j);
                             out.startTag(null, "op");
                             out.attribute(null, "n", Integer.toString(op.getOp()));
-                            out.attribute(null, "t", Long.toString(op.getTime()));
-                            out.attribute(null, "d", Integer.toString(op.getDuration()));
+                            if (op.getMode() != AppOpsManager.MODE_ALLOWED) {
+                                out.attribute(null, "m", Integer.toString(op.getMode()));
+                            }
+                            long time = op.getTime();
+                            if (time != 0) {
+                                out.attribute(null, "t", Long.toString(time));
+                            }
+                            time = op.getRejectTime();
+                            if (time != 0) {
+                                out.attribute(null, "r", Long.toString(time));
+                            }
+                            int dur = op.getDuration();
+                            if (dur != 0) {
+                                out.attribute(null, "d", Integer.toString(dur));
+                            }
                             out.endTag(null, "op");
                         }
                         out.endTag(null, "uid");
@@ -532,6 +611,7 @@
 
         synchronized (this) {
             pw.println("Current AppOps Service state:");
+            final long now = System.currentTimeMillis();
             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);
@@ -539,10 +619,16 @@
                     pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
                     for (int j=0; j<ops.size(); j++) {
                         Op op = ops.valueAt(j);
-                        pw.print("      "); pw.print(AppOpsManager.opToString(op.op));
-                        pw.print(": time=");
-                        TimeUtils.formatDuration(System.currentTimeMillis()-op.time, pw);
-                        pw.print(" ago");
+                        pw.print("      "); pw.print(AppOpsManager.opToName(op.op));
+                        pw.print(": mode="); pw.print(op.mode);
+                        if (op.time != 0) {
+                            pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
+                            pw.print(" ago");
+                        }
+                        if (op.rejectTime != 0) {
+                            pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
+                            pw.print(" ago");
+                        }
                         if (op.duration == -1) {
                             pw.println(" (running)");
                         } else {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index b351fc2..f63719e 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -529,9 +529,6 @@
         }
 
         public boolean callLocationChangedLocked(Location location) {
-            if (!reportLocationAccessNoThrow(mUid, mPackageName, mAllowedResolutionLevel)) {
-                return true;
-            }
             if (mListener != null) {
                 try {
                     synchronized (this) {
@@ -802,14 +799,20 @@
         }
     }
 
-    boolean reportLocationAccessNoThrow(int uid, String packageName, int allowedResolutionLevel) {
-        int op;
+    public static int resolutionLevelToOp(int allowedResolutionLevel) {
         if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
             if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
-                op = AppOpsManager.OP_COARSE_LOCATION;
+                return AppOpsManager.OP_COARSE_LOCATION;
             } else {
-                op = AppOpsManager.OP_FINE_LOCATION;
+                return AppOpsManager.OP_FINE_LOCATION;
             }
+        }
+        return -1;
+    }
+
+    boolean reportLocationAccessNoThrow(int uid, String packageName, int allowedResolutionLevel) {
+        int op = resolutionLevelToOp(allowedResolutionLevel);
+        if (op >= 0) {
             if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
@@ -818,13 +821,8 @@
     }
 
     boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
-        int op;
-        if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
-            if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
-                op = AppOpsManager.OP_COARSE_LOCATION;
-            } else {
-                op = AppOpsManager.OP_FINE_LOCATION;
-            }
+        int op = resolutionLevelToOp(allowedResolutionLevel);
+        if (op >= 0) {
             if (mAppOps.checkOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
@@ -1019,11 +1017,14 @@
         if (records != null) {
             for (UpdateRecord record : records) {
                 if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
-                    LocationRequest locationRequest = record.mRequest;
-                    providerRequest.locationRequests.add(locationRequest);
-                    if (locationRequest.getInterval() < providerRequest.interval) {
-                        providerRequest.reportLocation = true;
-                        providerRequest.interval = locationRequest.getInterval();
+                    if (checkLocationAccess(record.mReceiver.mUid, record.mReceiver.mPackageName,
+                            record.mReceiver.mAllowedResolutionLevel)) {
+                        LocationRequest locationRequest = record.mRequest;
+                        providerRequest.locationRequests.add(locationRequest);
+                        if (locationRequest.getInterval() < providerRequest.interval) {
+                            providerRequest.reportLocation = true;
+                            providerRequest.interval = locationRequest.getInterval();
+                        }
                     }
                 }
             }
@@ -1144,9 +1145,6 @@
      * and consistency requirements.
      *
      * @param request the LocationRequest from which to create a sanitized version
-     * @param shouldBeCoarse whether the sanitized version should be held to coarse resolution
-     * constraints
-     * @param fastestCoarseIntervalMS minimum interval allowed for coarse resolution
      * @return a version of request that meets the given resolution and consistency requirements
      * @hide
      */
@@ -1340,16 +1338,18 @@
         final int uid = Binder.getCallingUid();
         final long identity = Binder.clearCallingIdentity();
         try {
-            if (!reportLocationAccessNoThrow(uid, packageName, allowedResolutionLevel)) {
-                return null;
-            }
-            
             if (mBlacklist.isBlacklisted(packageName)) {
                 if (D) Log.d(TAG, "not returning last loc for blacklisted app: " +
                         packageName);
                 return null;
             }
 
+            if (!reportLocationAccessNoThrow(uid, packageName, allowedResolutionLevel)) {
+                if (D) Log.d(TAG, "not returning last loc for no op app: " +
+                        packageName);
+                return null;
+            }
+
             synchronized (mLock) {
                 // Figure out the provider. Either its explicitly request (deprecated API's),
                 // or use the fused provider
@@ -1402,7 +1402,8 @@
         }
         long identity = Binder.clearCallingIdentity();
         try {
-            mGeofenceManager.addFence(sanitizedRequest, geofence, intent, uid, packageName);
+            mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
+                    uid, packageName);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1703,6 +1704,13 @@
                 continue;
             }
 
+            if (!reportLocationAccessNoThrow(receiver.mUid, receiver.mPackageName,
+                    receiver.mAllowedResolutionLevel)) {
+                if (D) Log.d(TAG, "skipping loc update for no op app: " +
+                        receiver.mPackageName);
+                continue;
+            }
+
             Location notifyLocation = null;
             if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                 notifyLocation = coarseLocation;  // use coarse location
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 5789a53..ad6eb4d 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -127,6 +128,7 @@
     private int mMulticastDisabled;
 
     private final IBatteryStats mBatteryStats;
+    private final AppOpsManager mAppOps;
 
     private boolean mEnableTrafficStatsPoll = false;
     private int mTrafficStatsPollToken = 0;
@@ -381,6 +383,7 @@
         mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
         mWifiStateMachine.enableRssiPolling(true);
         mBatteryStats = BatteryStatsService.getService();
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
 
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
         Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
@@ -845,10 +848,15 @@
      * a list of {@link ScanResult} objects.
      * @return the list of results
      */
-    public List<ScanResult> getScanResults() {
+    public List<ScanResult> getScanResults(String callingPackage) {
         enforceAccessPermission();
         int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
+        if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                != AppOpsManager.MODE_ALLOWED) {
+            return new ArrayList<ScanResult>();
+        }
         try {
             int currentUser = ActivityManager.getCurrentUser();
             if (userId != currentUser) {
diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java
index f9be719..e24bf76 100644
--- a/services/java/com/android/server/location/GeofenceManager.java
+++ b/services/java/com/android/server/location/GeofenceManager.java
@@ -21,6 +21,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -68,6 +69,7 @@
 
     private final Context mContext;
     private final LocationManager mLocationManager;
+    private final AppOpsManager mAppOps;
     private final PowerManager.WakeLock mWakeLock;
     private final GeofenceHandler mHandler;
     private final LocationBlacklist mBlacklist;
@@ -107,6 +109,7 @@
     public GeofenceManager(Context context, LocationBlacklist blacklist) {
         mContext = context;
         mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mHandler = new GeofenceHandler();
@@ -114,14 +117,14 @@
     }
 
     public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            int uid, String packageName) {
+            int allowedResolutionLevel, int uid, String packageName) {
         if (D) {
             Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
                     + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
         }
 
         GeofenceState state = new GeofenceState(geofence,
-                request.getExpireAt(), packageName, intent);
+                request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
         synchronized (mLock) {
             // first make sure it doesn't already exist
             for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -261,6 +264,18 @@
                     continue;
                 }
 
+                int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
+                if (op >= 0) {
+                    if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
+                            state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
+                        if (D) {
+                            Slog.d(TAG, "skipping geofence processing for no op app: "
+                                    + state.mPackageName);
+                        }
+                        continue;
+                    }
+                }
+
                 needUpdates = true;
                 if (location != null) {
                     int event = state.processLocation(location);
diff --git a/services/java/com/android/server/location/GeofenceState.java b/services/java/com/android/server/location/GeofenceState.java
index 11705ff..3ebe20a 100644
--- a/services/java/com/android/server/location/GeofenceState.java
+++ b/services/java/com/android/server/location/GeofenceState.java
@@ -35,6 +35,8 @@
     public final Geofence mFence;
     private final Location mLocation;
     public final long mExpireAt;
+    public final int mAllowedResolutionLevel;
+    public final int mUid;
     public final String mPackageName;
     public final PendingIntent mIntent;
 
@@ -42,12 +44,14 @@
     double mDistanceToCenter;  // current distance to center of fence
 
     public GeofenceState(Geofence fence, long expireAt,
-            String packageName, PendingIntent intent) {
+            int allowedResolutionLevel, int uid, String packageName, PendingIntent intent) {
         mState = STATE_UNKNOWN;
         mDistanceToCenter = Double.MAX_VALUE;
 
         mFence = fence;
         mExpireAt = expireAt;
+        mAllowedResolutionLevel = allowedResolutionLevel;
+        mUid = uid;
         mPackageName = packageName;
         mIntent = intent;
 
diff --git a/services/java/com/android/server/location/LocationBlacklist.java b/services/java/com/android/server/location/LocationBlacklist.java
index 2437a37..6f22689 100644
--- a/services/java/com/android/server/location/LocationBlacklist.java
+++ b/services/java/com/android/server/location/LocationBlacklist.java
@@ -67,9 +67,9 @@
 
     private void reloadBlacklistLocked() {
         mWhitelist = getStringArrayLocked(WHITELIST_CONFIG_NAME);
-        Slog.i(TAG, "whitelist: " + Arrays.toString(mWhitelist));
+        if (D) Slog.d(TAG, "whitelist: " + Arrays.toString(mWhitelist));
         mBlacklist = getStringArrayLocked(BLACKLIST_CONFIG_NAME);
-        Slog.i(TAG, "blacklist: " + Arrays.toString(mBlacklist));
+        if (D) Slog.d(TAG, "blacklist: " + Arrays.toString(mBlacklist));
     }
 
     private void reloadBlacklist() {
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index b893062..626002d 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1348,7 +1348,8 @@
                 continue;
             }
 
-            if (!ps.grantedPermissions
+            final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+            if (!gp.grantedPermissions
                     .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
                 continue;
             }
@@ -2918,8 +2919,9 @@
     private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps,
             String[] permissions, boolean[] tmp, int flags, int userId) {
         int numMatch = 0;
+        final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
         for (int i=0; i<permissions.length; i++) {
-            if (ps.grantedPermissions.contains(permissions[i])) {
+            if (gp.grantedPermissions.contains(permissions[i])) {
                 tmp[i] = true;
                 numMatch++;
             } else {