New "app ops" service.

Initial implementation, tracking use of the vibrator, GPS,
and location reports.

Also includes an update to battery stats to also keep track of
vibrator usage (since I had to be in the vibrator code anyway
to instrument it).

The service itself is only half-done.  Currently no API to
retrieve the data (which once there will allow us to show you
which apps are currently causing the GPS to run and who has
recently accessed your location), it doesn't persist its data
like it should, and no way to tell it to reject app requests
for various operations.

But hey, it's a start!

Change-Id: I05b8d76cc4a4f7f37bc758c1701f51f9e0550e15
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
new file mode 100644
index 0000000..5ad4be8
--- /dev/null
+++ b/services/java/com/android/server/AppOpsService.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.android.server;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.internal.app.IAppOpsService;
+
+public class AppOpsService extends IAppOpsService.Stub {
+    static final String TAG = "AppOps";
+
+    Context mContext;
+    final AtomicFile mFile;
+
+    final SparseArray<HashMap<String, Ops>> mUidOps
+            = new SparseArray<HashMap<String, Ops>>();
+
+    final static class Ops extends SparseArray<Op> {
+        public final String packageName;
+
+        public Ops(String _packageName) {
+            packageName = _packageName;
+        }
+    }
+
+    final static class Op {
+        public final int op;
+        public int duration;
+        public long time;
+
+        public Op(int _op) {
+            op = _op;
+        }
+    }
+
+    public AppOpsService() {
+        mFile = new AtomicFile(new File(Environment.getSecureDataDirectory(), "appops.xml"));
+    }
+    
+    public void publish(Context context) {
+        mContext = context;
+        ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+    }
+
+    public void shutdown() {
+        Slog.w(TAG, "Writing app ops before shutdown...");
+    }
+
+    @Override
+    public int noteOperation(int code, int uid, String packageName) {
+        uid = handleIncomingUid(uid);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName);
+            if (op == null) {
+                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;
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    @Override
+    public int startOperation(int code, int uid, String packageName) {
+        uid = handleIncomingUid(uid);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName);
+            if (op == null) {
+                return AppOpsManager.MODE_IGNORED;
+            }
+            if (op.duration == -1) {
+                Slog.w(TAG, "Starting op not finished: uid " + uid + " pkg " + packageName
+                        + " code " + code + " time=" + op.time + " duration=" + op.duration);
+            }
+            op.time = System.currentTimeMillis();
+            op.duration = -1;
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    @Override
+    public void finishOperation(int code, int uid, String packageName) {
+        uid = handleIncomingUid(uid);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName);
+            if (op == null) {
+                return;
+            }
+            if (op.duration != -1) {
+                Slog.w(TAG, "Ignoring finishing op not started: uid " + uid + " pkg " + packageName
+                        + " code " + code + " time=" + op.time + " duration=" + op.duration);
+                return;
+            }
+            op.duration = (int)(System.currentTimeMillis() - op.time);
+        }
+    }
+
+    @Override
+    public int noteTimedOperation(int code, int uid, String packageName, int duration) {
+        uid = handleIncomingUid(uid);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName);
+            if (op == null) {
+                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 = duration;
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    @Override
+    public void earlyFinishOperation(int code, int uid, String packageName) {
+        uid = handleIncomingUid(uid);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName);
+            if (op == null) {
+                return;
+            }
+            if (op.duration != -1) {
+                Slog.w(TAG, "Noting timed op not finished: uid " + uid + " pkg " + packageName
+                        + " code " + code + " time=" + op.time + " duration=" + op.duration);
+            }
+            int newDuration = (int)(System.currentTimeMillis() - op.time);
+            if (newDuration < op.duration) {
+                op.duration = newDuration;
+            }
+        }
+    }
+
+    private int handleIncomingUid(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return uid;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return uid;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        return uid;
+    }
+
+    private Op getOpLocked(int code, int uid, String packageName) {
+        HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+        if (pkgOps == null) {
+            pkgOps = new HashMap<String, Ops>();
+            mUidOps.put(uid, pkgOps);
+        }
+        Ops ops = pkgOps.get(packageName);
+        if (ops == null) {
+            // This is the first time we have seen this package name under this uid,
+            // so let's make sure it is valid.
+            // XXX for now we always allow null through until we can fix everything
+            // to provide the name.
+            if (packageName != null) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    int pkgUid = -1;
+                    try {
+                        pkgUid = mContext.getPackageManager().getPackageUid(packageName,
+                                UserHandle.getUserId(uid));
+                    } catch (NameNotFoundException e) {
+                    }
+                    if (pkgUid != uid) {
+                        // Oops!  The package name is not valid for the uid they are calling
+                        // under.  Abort.
+                        Slog.w(TAG, "Bad call: specified package " + packageName
+                                + " under uid " + uid + " but it is really " + pkgUid);
+                        return null;
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+            ops = new Ops(packageName);
+            pkgOps.put(packageName, ops);
+        }
+        Op op = ops.get(code);
+        if (op == null) {
+            op = new Op(code);
+            ops.put(code, op);
+        }
+        return op;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump ApOps service from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (this) {
+            pw.println("Current AppOps Service state:");
+            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);
+                for (Ops ops : pkgOps.values()) {
+                    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");
+                        if (op.duration == -1) {
+                            pw.println(" (running)");
+                        } else {
+                            pw.print("; duration=");
+                                    TimeUtils.formatDuration(op.duration, pw);
+                                    pw.println();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 83b94e2..9e40dc5 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -53,6 +54,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
@@ -60,6 +62,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -124,6 +127,7 @@
     private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
 
     private final Context mContext;
+    private final AppOpsManager mAppOps;
 
     // used internally for synchronization
     private final Object mLock = new Object();
@@ -187,6 +191,7 @@
     public LocationManagerService(Context context) {
         super();
         mContext = context;
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
 
         if (D) Log.d(TAG, "Constructed");
 
@@ -524,6 +529,10 @@
         }
 
         public boolean callLocationChangedLocked(Location location) {
+            if (mAppOps.noteOpNoThrow(AppOpsManager.OP_LOCATION, mUid, mPackageName)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return true;
+            }
             if (mListener != null) {
                 try {
                     synchronized (this) {
@@ -1162,7 +1171,7 @@
     private Receiver checkListenerOrIntent(ILocationListener listener, PendingIntent intent,
             int pid, int uid, String packageName) {
         if (intent == null && listener == null) {
-            throw new IllegalArgumentException("need eiter listener or intent");
+            throw new IllegalArgumentException("need either listener or intent");
         } else if (intent != null && listener != null) {
             throw new IllegalArgumentException("cannot register both listener and intent");
         } else if (intent != null) {
@@ -1185,11 +1194,14 @@
 
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
-        Receiver recevier = checkListenerOrIntent(listener, intent, pid, uid, packageName);
-
         // providers may use public location API's, need to clear identity
         long identity = Binder.clearCallingIdentity();
         try {
+            // We don't check for MODE_IGNORED here; we will do that when we go to deliver
+            // a location.
+            mAppOps.noteOp(AppOpsManager.OP_LOCATION, uid, packageName);
+            Receiver recevier = checkListenerOrIntent(listener, intent, pid, uid, packageName);
+
             synchronized (mLock) {
                 requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName);
             }
@@ -1296,8 +1308,14 @@
                 request.getProvider());
         // no need to sanitize this request, as only the provider name is used
 
-        long identity = Binder.clearCallingIdentity();
+        final int uid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
         try {
+            if (mAppOps.noteOp(AppOpsManager.OP_LOCATION, uid, packageName)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return null;
+            }
+            
             if (mBlacklist.isBlacklisted(packageName)) {
                 if (D) Log.d(TAG, "not returning last loc for blacklisted app: " +
                         packageName);
@@ -1381,13 +1399,24 @@
 
 
     @Override
-    public boolean addGpsStatusListener(IGpsStatusListener listener) {
+    public boolean addGpsStatusListener(IGpsStatusListener listener, String packageName) {
         if (mGpsStatusProvider == null) {
             return false;
         }
         checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
                 LocationManager.GPS_PROVIDER);
 
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mAppOps.noteOp(AppOpsManager.OP_LOCATION, uid, packageName)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
         try {
             mGpsStatusProvider.addGpsStatusListener(listener);
         } catch (RemoteException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a7b502a..c33eb2b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -261,7 +261,6 @@
             ServiceManager.addService(Context.USER_SERVICE,
                     UserManagerService.getInstance());
 
-
             mContentResolver = context.getContentResolver();
 
             // The AccountManager must come before the ContentService
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index df91dec..69379f1 100644
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +31,7 @@
 import android.os.RemoteException;
 import android.os.IBinder;
 import android.os.Binder;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.Vibrator;
@@ -39,6 +41,9 @@
 import android.util.Slog;
 import android.view.InputDevice;
 
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.ListIterator;
@@ -54,6 +59,8 @@
 
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
+    private final IAppOpsService mAppOpsService;
+    private final IBatteryStats mBatteryStatsService;
     private InputManager mIm;
 
     volatile VibrateThread mThread;
@@ -64,6 +71,8 @@
     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
 
+    private int mCurVibUid = -1;
+
     native static boolean vibratorExists();
     native static void vibratorOn(long milliseconds);
     native static void vibratorOff();
@@ -75,23 +84,25 @@
         private final long[]  mPattern;
         private final int     mRepeat;
         private final int     mUid;
+        private final String  mPackageName;
 
-        Vibration(IBinder token, long millis, int uid) {
-            this(token, millis, null, 0, uid);
+        Vibration(IBinder token, long millis, int uid, String packageName) {
+            this(token, millis, null, 0, uid, packageName);
         }
 
-        Vibration(IBinder token, long[] pattern, int repeat, int uid) {
-            this(token, 0, pattern, repeat, uid);
+        Vibration(IBinder token, long[] pattern, int repeat, int uid, String packageName) {
+            this(token, 0, pattern, repeat, uid, packageName);
         }
 
         private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat, int uid) {
+                int repeat, int uid, String packageName) {
             mToken = token;
             mTimeout = millis;
             mStartTime = SystemClock.uptimeMillis();
             mPattern = pattern;
             mRepeat = repeat;
             mUid = uid;
+            mPackageName = packageName;
         }
 
         public void binderDied() {
@@ -131,6 +142,9 @@
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mWakeLock.setReferenceCounted(true);
 
+        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
         mVibrations = new LinkedList<Vibration>();
 
         IntentFilter filter = new IntentFilter();
@@ -164,7 +178,7 @@
         return doVibratorExists();
     }
 
-    public void vibrate(long milliseconds, IBinder token) {
+    public void vibrate(String packageName, long milliseconds, IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
@@ -180,12 +194,18 @@
             return;
         }
 
-        Vibration vib = new Vibration(token, milliseconds, uid);
-        synchronized (mVibrations) {
-            removeVibrationLocked(token);
-            doCancelVibrateLocked();
-            mCurrentVibration = vib;
-            startVibrationLocked(vib);
+        Vibration vib = new Vibration(token, milliseconds, uid, packageName);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mVibrations) {
+                removeVibrationLocked(token);
+                doCancelVibrateLocked();
+                mCurrentVibration = vib;
+                startVibrationLocked(vib);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -199,7 +219,7 @@
         return true;
     }
 
-    public void vibratePattern(long[] pattern, int repeat, IBinder token) {
+    public void vibratePattern(String packageName, long[] pattern, int repeat, IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
@@ -224,7 +244,7 @@
                 return;
             }
 
-            Vibration vib = new Vibration(token, pattern, repeat, uid);
+            Vibration vib = new Vibration(token, pattern, repeat, uid, packageName);
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
@@ -291,11 +311,13 @@
         }
         doVibratorOff();
         mH.removeCallbacks(mVibrationRunnable);
+        reportFinishVibrationLocked();
     }
 
     // Lock held on mVibrations
     private void startNextVibrationLocked() {
         if (mVibrations.size() <= 0) {
+            reportFinishVibrationLocked();
             mCurrentVibration = null;
             return;
         }
@@ -305,8 +327,19 @@
 
     // Lock held on mVibrations
     private void startVibrationLocked(final Vibration vib) {
+        try {
+            int mode = mAppOpsService.startOperation(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);
+                }
+                mH.post(mVibrationRunnable);
+                return;
+            }
+        } catch (RemoteException e) {
+        }
         if (vib.mTimeout != 0) {
-            doVibratorOn(vib.mTimeout);
+            doVibratorOn(vib.mTimeout, vib.mUid);
             mH.postDelayed(mVibrationRunnable, vib.mTimeout);
         } else {
             // mThread better be null here. doCancelVibrate should always be
@@ -316,6 +349,17 @@
         }
     }
 
+    private void reportFinishVibrationLocked() {
+        if (mCurrentVibration != null) {
+            try {
+                mAppOpsService.finishOperation(AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
+                        mCurrentVibration.mPackageName);
+            } catch (RemoteException e) {
+            }
+            mCurrentVibration = null;
+        }
+    }
+
     // Lock held on mVibrations
     private Vibration removeVibrationLocked(IBinder token) {
         ListIterator<Vibration> iter = mVibrations.listIterator(0);
@@ -413,8 +457,13 @@
         return vibratorExists();
     }
 
-    private void doVibratorOn(long millis) {
+    private void doVibratorOn(long millis, int uid) {
         synchronized (mInputDeviceVibrators) {
+            try {
+                mBatteryStatsService.noteVibratorOn(uid, millis);
+                mCurVibUid = uid;
+            } catch (RemoteException e) {
+            }
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
                 for (int i = 0; i < vibratorCount; i++) {
@@ -428,6 +477,13 @@
 
     private void doVibratorOff() {
         synchronized (mInputDeviceVibrators) {
+            if (mCurVibUid >= 0) {
+                try {
+                    mBatteryStatsService.noteVibratorOff(mCurVibUid);
+                } catch (RemoteException e) {
+                }
+                mCurVibUid = -1;
+            }
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
                 for (int i = 0; i < vibratorCount; i++) {
@@ -470,10 +526,11 @@
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
             synchronized (this) {
+                final long[] pattern = mVibration.mPattern;
+                final int len = pattern.length;
+                final int repeat = mVibration.mRepeat;
+                final int uid = mVibration.mUid;
                 int index = 0;
-                long[] pattern = mVibration.mPattern;
-                int len = pattern.length;
-                int repeat = mVibration.mRepeat;
                 long duration = 0;
 
                 while (!mDone) {
@@ -493,7 +550,7 @@
                         // duration is saved for delay() at top of loop
                         duration = pattern[index++];
                         if (duration > 0) {
-                            VibratorService.this.doVibratorOn(duration);
+                            VibratorService.this.doVibratorOn(duration, uid);
                         }
                     } else {
                         if (repeat < 0) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 62af91e..b08fc28 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -21,6 +21,7 @@
 import com.android.internal.R;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.ProcessStats;
+import com.android.server.AppOpsService;
 import com.android.server.AttributeCache;
 import com.android.server.IntentResolver;
 import com.android.server.ProcessMap;
@@ -619,9 +620,14 @@
     final BatteryStatsService mBatteryStatsService;
     
     /**
-     * information about component usage
+     * Information about component usage
      */
     final UsageStatsService mUsageStatsService;
+    
+    /**
+     * Information about and control over application operations
+     */
+    final AppOpsService mAppOpsService;
 
     /**
      * Current configuration information.  HistoryRecord objects are given
@@ -1450,7 +1456,8 @@
         
         m.mBatteryStatsService.publish(context);
         m.mUsageStatsService.publish(context);
-        
+        m.mAppOpsService.publish(context);
+
         synchronized (thr) {
             thr.mReady = true;
             thr.notifyAll();
@@ -1613,9 +1620,10 @@
         mOnBattery = DEBUG_POWER ? true
                 : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
         mBatteryStatsService.getActiveStatistics().setCallback(this);
-        
+
         mUsageStatsService = new UsageStatsService(new File(
                 systemDir, "usagestats").toString());
+        mAppOpsService = new AppOpsService();
         mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
 
         // User 0 is the first and only user that runs at boot.
@@ -7174,7 +7182,8 @@
                 }
             }
         }
-        
+
+        mAppOpsService.shutdown();
         mUsageStatsService.shutdown();
         mBatteryStatsService.shutdown();
         
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index ab20208..d19c7f6 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -144,6 +144,20 @@
         }
     }
     
+    public void noteVibratorOn(int uid, long durationMillis) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteVibratorOnLocked(uid, durationMillis);
+        }
+    }
+
+    public void noteVibratorOff(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteVibratorOffLocked(uid);
+        }
+    }
+
     public void noteStartGps(int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 7f059f5..f1739d5 100644
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -17,6 +17,7 @@
 package com.android.server.location;
 
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -56,6 +57,8 @@
 import android.telephony.gsm.GsmCellLocation;
 import android.util.Log;
 import android.util.NtpTrustedTime;
+
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.location.GpsNetInitiatedHandler;
 import com.android.internal.location.ProviderProperties;
@@ -305,6 +308,7 @@
     private final PendingIntent mWakeupIntent;
     private final PendingIntent mTimeoutIntent;
 
+    private final IAppOpsService mAppOpsService;
     private final IBatteryStats mBatteryStats;
 
     // only modified on handler thread
@@ -434,6 +438,10 @@
 
         mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
+        // App ops service to keep track of who is accessing the GPS
+        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(
+                Context.APP_OPS_SERVICE));
+
         // Battery statistics service to be notified when GPS turns on or off
         mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
 
@@ -863,6 +871,7 @@
             }
             if (newUid) {
                 try {
+                    mAppOpsService.startOperation(AppOpsManager.OP_GPS, uid1, null);
                     mBatteryStats.noteStartGps(uid1);
                 } catch (RemoteException e) {
                     Log.w(TAG, "RemoteException", e);
@@ -882,6 +891,7 @@
             if (oldUid) {
                 try {
                     mBatteryStats.noteStopGps(uid1);
+                    mAppOpsService.finishOperation(AppOpsManager.OP_GPS, uid1, null);
                 } catch (RemoteException e) {
                     Log.w(TAG, "RemoteException", e);
                 }