Merge "New "app ops" service."
diff --git a/Android.mk b/Android.mk
index 294a2fe..462d6ac 100644
--- a/Android.mk
+++ b/Android.mk
@@ -166,6 +166,7 @@
 	core/java/android/speech/IRecognitionService.aidl \
 	core/java/android/speech/tts/ITextToSpeechCallback.aidl \
 	core/java/android/speech/tts/ITextToSpeechService.aidl \
+	core/java/com/android/internal/app/IAppOpsService.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
 	core/java/com/android/internal/app/IUsageStats.aidl \
 	core/java/com/android/internal/app/IMediaContainerService.aidl \
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
new file mode 100644
index 0000000..7210df4
--- /dev/null
+++ b/core/java/android/app/AppOpsManager.java
@@ -0,0 +1,110 @@
+/*
+ * 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 com.android.internal.app.IAppOpsService;
+
+import android.content.Context;
+import android.os.Process;
+import android.os.RemoteException;
+
+/** @hide */
+public class AppOpsManager {
+    final Context mContext;
+    final IAppOpsService mService;
+
+    public static final int MODE_ALLOWED = 0;
+    public static final int MODE_IGNORED = 1;
+    public static final int MODE_ERRORED = 2;
+
+    public static final int OP_LOCATION = 0;
+    public static final int OP_GPS = 1;
+    public static final int OP_VIBRATE = 2;
+
+    public static String opToString(int op) {
+        switch (op) {
+            case OP_LOCATION: return "LOCATION";
+            case OP_GPS: return "GPS";
+            case OP_VIBRATE: return "VIBRATE";
+            default: return "Unknown(" + op + ")";
+        }
+    }
+
+    public AppOpsManager(Context context, IAppOpsService service) {
+        mContext = context;
+        mService = service;
+    }
+
+    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;
+    }
+
+    public int noteOpNoThrow(int op, int uid, String packageName) {
+        try {
+            return mService.noteOperation(op, uid, packageName);
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
+    public int noteOp(int op) {
+        return noteOp(op, Process.myUid(), mContext.getPackageName());
+    }
+
+    public int startOp(int op, int uid, String packageName) {
+        try {
+            int mode = mService.startOperation(op, uid, packageName);
+            if (mode == MODE_ERRORED) {
+                throw new SecurityException("Operation not allowed");
+            }
+            return mode;
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
+    public int startOpNoThrow(int op, int uid, String packageName) {
+        try {
+            return mService.startOperation(op, uid, packageName);
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
+    public int startOp(int op) {
+        return startOp(op, Process.myUid(), mContext.getPackageName());
+    }
+
+    public void finishOp(int op, int uid, String packageName) {
+        try {
+            mService.finishOperation(op, uid, packageName);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public void finishOp(int op) {
+        finishOp(op, Process.myUid(), mContext.getPackageName());
+    }
+}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7431765..03d1a3f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -142,6 +142,21 @@
     }
 
     @Override
+    public int getPackageUid(String packageName, int userHandle)
+            throws NameNotFoundException {
+        try {
+            int uid = mPM.getPackageUid(packageName, userHandle);
+            if (uid >= 0) {
+                return uid;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Package manager has died", e);
+        }
+
+        throw new NameNotFoundException(packageName);
+    }
+
+    @Override
     public PermissionInfo getPermissionInfo(String name, int flags)
             throws NameNotFoundException {
         try {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f895ccc..8ef708c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -47,11 +47,9 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.hardware.ISerialManager;
-import android.hardware.SensorManager;
 import android.hardware.SerialManager;
 import android.hardware.SystemSensorManager;
 import android.hardware.display.DisplayManager;
-import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
@@ -109,6 +107,8 @@
 import android.accounts.AccountManager;
 import android.accounts.IAccountManager;
 import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.os.IDropBoxManagerService;
 
 import java.io.File;
@@ -499,7 +499,7 @@
 
         registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
-                    return new SystemVibrator();
+                    return new SystemVibrator(ctx);
                 }});
 
         registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER);
@@ -530,11 +530,18 @@
                 }});
 
         registerService(USER_SERVICE, new ServiceFetcher() {
-            public Object getService(ContextImpl ctx) {
+            public Object createService(ContextImpl ctx) {
                 IBinder b = ServiceManager.getService(USER_SERVICE);
                 IUserManager service = IUserManager.Stub.asInterface(b);
                 return new UserManager(ctx, service);
             }});
+
+        registerService(APP_OPS_SERVICE, new ServiceFetcher() {
+            public Object createService(ContextImpl ctx) {
+                IBinder b = ServiceManager.getService(APP_OPS_SERVICE);
+                IAppOpsService service = IAppOpsService.Stub.asInterface(b);
+                return new AppOpsManager(ctx, service);
+            }});
     }
 
     static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 257f84e..c777250 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2289,6 +2289,18 @@
     public static final String USER_SERVICE = "user";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.app.AppOpsManager} for tracking application operations
+     * on the device.
+     *
+     * @see #getSystemService
+     * @see android.app.AppOpsManager
+     *
+     * @hide
+     */
+    public static final String APP_OPS_SERVICE = "appops";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8ba1988..cdd9195 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1280,6 +1280,22 @@
             throws NameNotFoundException;
 
     /**
+     * @hide Return the uid associated with the given package name for the
+     * given user.
+     *
+     * <p>Throws {@link NameNotFoundException} if a package with the given
+     * name can not be found on the system.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of the
+     *                    desired package.
+     * @param userHandle The user handle identifier to look up the package under.
+     *
+     * @return Returns an integer uid who owns the given package name.
+     */
+    public abstract int getPackageUid(String packageName, int userHandle)
+            throws NameNotFoundException;
+
+    /**
      * Retrieve all of the information we know about a particular permission.
      *
      * <p>Throws {@link NameNotFoundException} if a permission with the given
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9821824..abbb6a1 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -93,6 +93,11 @@
     public static final int VIDEO_TURNED_ON = 8;
 
     /**
+     * A constant indicating a vibrator on timer
+     */
+    public static final int VIBRATOR_ON = 9;
+
+    /**
      * Include all of the data in the stats, including previously saved data.
      */
     public static final int STATS_SINCE_CHARGED = 0;
@@ -131,6 +136,7 @@
     private static final String APK_DATA = "apk";
     private static final String PROCESS_DATA = "pr";
     private static final String SENSOR_DATA = "sr";
+    private static final String VIBRATOR_DATA = "vib";
     private static final String WAKELOCK_DATA = "wl";
     private static final String KERNEL_WAKELOCK_DATA = "kwl";
     private static final String NETWORK_DATA = "nt";
@@ -277,6 +283,7 @@
                                                   int which);
         public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
         public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
+        public abstract Timer getVibratorOnTimer();
 
         /**
          * Note that these must match the constants in android.os.PowerManager.
@@ -1395,6 +1402,16 @@
                 }
             }
 
+            Timer vibTimer = u.getVibratorOnTimer();
+            if (vibTimer != null) {
+                // Convert from microseconds to milliseconds with rounding
+                long totalTime = (vibTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+                int count = vibTimer.getCountLocked(which);
+                if (totalTime != 0) {
+                    dumpLine(pw, uid, category, VIBRATOR_DATA, totalTime, count);
+                }
+            }
+
             Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
             if (processStats.size() > 0) {
                 for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
@@ -1919,6 +1936,26 @@
                 }
             }
 
+            Timer vibTimer = u.getVibratorOnTimer();
+            if (vibTimer != null) {
+                // Convert from microseconds to milliseconds with rounding
+                long totalTime = (vibTimer.getTotalTimeLocked(
+                        batteryRealtime, which) + 500) / 1000;
+                int count = vibTimer.getCountLocked(which);
+                //timer.logState();
+                if (totalTime != 0) {
+                    sb.setLength(0);
+                    sb.append(prefix);
+                    sb.append("    Vibrator: ");
+                    formatTimeMs(sb, totalTime);
+                    sb.append("realtime (");
+                    sb.append(count);
+                    sb.append(" times)");
+                    pw.println(sb.toString());
+                    uidActivity = true;
+                }
+            }
+
             Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
             if (processStats.size() > 0) {
                 for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 2c2fe8a..15cedf9 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -20,8 +20,8 @@
 interface IVibratorService
 {
     boolean hasVibrator();
-    void vibrate(long milliseconds, IBinder token);
-    void vibratePattern(in long[] pattern, int repeat, IBinder token);
+    void vibrate(String packageName, long milliseconds, IBinder token);
+    void vibratePattern(String packageName, in long[] pattern, int repeat, IBinder token);
     void cancelVibrate(IBinder token);
 }
 
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 7c5a47e..54ea385 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.content.Context;
 import android.util.Log;
 
 /**
@@ -26,10 +27,18 @@
 public class SystemVibrator extends Vibrator {
     private static final String TAG = "Vibrator";
 
+    private final String mPackageName;
     private final IVibratorService mService;
     private final Binder mToken = new Binder();
 
     public SystemVibrator() {
+        mPackageName = null;
+        mService = IVibratorService.Stub.asInterface(
+                ServiceManager.getService("vibrator"));
+    }
+
+    public SystemVibrator(Context context) {
+        mPackageName = context.getPackageName();
         mService = IVibratorService.Stub.asInterface(
                 ServiceManager.getService("vibrator"));
     }
@@ -54,7 +63,7 @@
             return;
         }
         try {
-            mService.vibrate(milliseconds, mToken);
+            mService.vibrate(mPackageName, milliseconds, mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
@@ -71,7 +80,7 @@
         // anyway
         if (repeat < pattern.length) {
             try {
-                mService.vibratePattern(pattern, repeat, mToken);
+                mService.vibratePattern(mPackageName, pattern, repeat, mToken);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to vibrate.", e);
             }
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index cc96152..d205253 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import java.io.PrintWriter;
+
 /**
  * Representation of a user on the device.
  */
@@ -152,6 +154,50 @@
     }
 
     /**
+     * Generate a text representation of the uid, breaking out its individual
+     * components -- user, app, isolated, etc.
+     * @hide
+     */
+    public static void formatUid(StringBuilder sb, int uid) {
+        if (uid < Process.FIRST_APPLICATION_UID) {
+            sb.append(uid);
+        } else {
+            sb.append('u');
+            sb.append(getUserId(uid));
+            final int appId = getAppId(uid);
+            if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
+                sb.append('i');
+                sb.append(appId - Process.FIRST_ISOLATED_UID);
+            } else {
+                sb.append('a');
+                sb.append(appId);
+            }
+        }
+    }
+
+    /**
+     * Generate a text representation of the uid, breaking out its individual
+     * components -- user, app, isolated, etc.
+     * @hide
+     */
+    public static void formatUid(PrintWriter pw, int uid) {
+        if (uid < Process.FIRST_APPLICATION_UID) {
+            pw.print(uid);
+        } else {
+            pw.print('u');
+            pw.print(getUserId(uid));
+            final int appId = getAppId(uid);
+            if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
+                pw.print('i');
+                pw.print(appId - Process.FIRST_ISOLATED_UID);
+            } else {
+                pw.print('a');
+                pw.print(appId);
+            }
+        }
+    }
+
+    /**
      * Returns the user id of the current process
      * @return user id of the current process
      * @hide
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
new file mode 100644
index 0000000..c934587
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.internal.app;
+
+interface IAppOpsService {
+    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 noteTimedOperation(int code, int uid, String packageName, int duration);
+    void earlyFinishOperation(int code, int uid, String packageName);
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1a76461..823e19f 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -37,6 +37,8 @@
     void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
     void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
 
+    void noteVibratorOn(int uid, long durationMillis);
+    void noteVibratorOff(int uid);
     void noteStartGps(int uid);
     void noteStopGps(int uid);
     void noteScreenOn();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 94e7a06..4d35a6b 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,14 +16,11 @@
 
 package com.android.internal.os;
 
-import static android.net.NetworkStats.IFACE_ALL;
-import static android.net.NetworkStats.UID_ALL;
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
 import android.os.BatteryManager;
@@ -49,7 +46,6 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 
-import com.android.internal.R;
 import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.util.JournaledFile;
 import com.google.android.collect.Sets;
@@ -87,7 +83,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 62 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 64 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -356,8 +352,8 @@
     }
 
     public static interface Unpluggable {
-        void unplug(long batteryUptime, long batteryRealtime);
-        void plug(long batteryUptime, long batteryRealtime);
+        void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
+        void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
     }
 
     /**
@@ -392,12 +388,12 @@
             out.writeInt(mUnpluggedCount);
         }
 
-        public void unplug(long batteryUptime, long batteryRealtime) {
+        public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             mUnpluggedCount = mPluggedCount;
             mCount.set(mPluggedCount);
         }
 
-        public void plug(long batteryUptime, long batteryRealtime) {
+        public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             mPluggedCount = mCount.get();
         }
 
@@ -587,7 +583,7 @@
             out.writeLong(mUnpluggedTime);
         }
 
-        public void unplug(long batteryUptime, long batteryRealtime) {
+        public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             if (DEBUG && mType < 0) {
                 Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
                         + " old mUnpluggedTime=" + mUnpluggedTime
@@ -602,7 +598,7 @@
             }
         }
 
-        public void plug(long batteryUptime, long batteryRealtime) {
+        public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             if (DEBUG && mType < 0) {
                 Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
                         + " old mTotalTime=" + mTotalTime);
@@ -731,7 +727,7 @@
         boolean mTrackingReportedValues;
 
         /*
-         * A sequnce counter, incremented once for each update of the stats.
+         * A sequence counter, incremented once for each update of the stats.
          */
         int mUpdateVersion;
 
@@ -786,8 +782,8 @@
             mCurrentReportedTotalTime = totalTime;
         }
 
-        public void unplug(long batteryUptime, long batteryRealtime) {
-            super.unplug(batteryUptime, batteryRealtime);
+        public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+            super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
             if (mTrackingReportedValues) {
                 mUnpluggedReportedTotalTime = mCurrentReportedTotalTime;
                 mUnpluggedReportedCount = mCurrentReportedCount;
@@ -795,8 +791,8 @@
             mInDischarge = true;
         }
 
-        public void plug(long batteryUptime, long batteryRealtime) {
-            super.plug(batteryUptime, batteryRealtime);
+        public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+            super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
             mInDischarge = false;
         }
 
@@ -849,6 +845,141 @@
     }
 
     /**
+     * A timer that increments in batches.  It does not run for durations, but just jumps
+     * for a pre-determined amount.
+     */
+    public static final class BatchTimer extends Timer {
+        final Uid mUid;
+
+        /**
+         * The last time at which we updated the timer.  This is in elapsed realtime microseconds.
+         */
+        long mLastAddedTime;
+
+        /**
+         * The last duration that we added to the timer.  This is in microseconds.
+         */
+        long mLastAddedDuration;
+
+        /**
+         * Whether we are currently in a discharge cycle.
+         */
+        boolean mInDischarge;
+
+        BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
+                boolean inDischarge, Parcel in) {
+            super(type, unpluggables, in);
+            mUid = uid;
+            mLastAddedTime = in.readLong();
+            mLastAddedDuration = in.readLong();
+            mInDischarge = inDischarge;
+        }
+
+        BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
+                boolean inDischarge) {
+            super(type, unpluggables);
+            mUid = uid;
+            mInDischarge = inDischarge;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, long batteryRealtime) {
+            super.writeToParcel(out, batteryRealtime);
+            out.writeLong(mLastAddedTime);
+            out.writeLong(mLastAddedDuration);
+        }
+
+        @Override
+        public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+            recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false);
+            mInDischarge = false;
+            super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
+        }
+
+        @Override
+        public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+            recomputeLastDuration(elapsedRealtime, false);
+            mInDischarge = true;
+            // If we are still within the last added duration, then re-added whatever remains.
+            if (mLastAddedTime == elapsedRealtime) {
+                mTotalTime += mLastAddedDuration;
+            }
+            super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+        }
+
+        @Override
+        public void logState(Printer pw, String prefix) {
+            super.logState(pw, prefix);
+            pw.println(prefix + "mLastAddedTime=" + mLastAddedTime
+                    + " mLastAddedDuration=" + mLastAddedDuration);
+        }
+
+        private long computeOverage(long curTime) {
+            if (mLastAddedTime > 0) {
+                return mLastTime + mLastAddedDuration - curTime;
+            }
+            return 0;
+        }
+
+        private void recomputeLastDuration(long curTime, boolean abort) {
+            final long overage = computeOverage(curTime);
+            if (overage > 0) {
+                // Aborting before the duration ran out -- roll back the remaining
+                // duration.  Only do this if currently discharging; otherwise we didn't
+                // actually add the time.
+                if (mInDischarge) {
+                    mTotalTime -= overage;
+                }
+                if (abort) {
+                    mLastAddedTime = 0;
+                } else {
+                    mLastAddedTime = curTime;
+                    mLastAddedDuration -= overage;
+                }
+            }
+        }
+
+        public void addDuration(BatteryStatsImpl stats, long durationMillis) {
+            final long now = SystemClock.elapsedRealtime() * 1000;
+            recomputeLastDuration(now, true);
+            mLastAddedTime = now;
+            mLastAddedDuration = durationMillis * 1000;
+            if (mInDischarge) {
+                mTotalTime += mLastAddedDuration;
+                mCount++;
+            }
+        }
+
+        public void abortLastDuration(BatteryStatsImpl stats) {
+            final long now = SystemClock.elapsedRealtime() * 1000;
+            recomputeLastDuration(now, true);
+        }
+
+        @Override
+        protected int computeCurrentCountLocked() {
+            return mCount;
+        }
+
+        @Override
+        protected long computeRunTimeLocked(long curBatteryRealtime) {
+            final long overage = computeOverage(SystemClock.elapsedRealtime() * 1000);
+            if (overage > 0) {
+                return mTotalTime = overage;
+            }
+            return mTotalTime;
+        }
+
+        @Override
+        boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+            final long now = SystemClock.elapsedRealtime() * 1000;
+            recomputeLastDuration(now, true);
+            boolean stillActive = mLastAddedTime == now;
+            super.reset(stats, !stillActive && detachIfReset);
+            return !stillActive;
+        }
+    }
+
+    /**
      * State for keeping track of timing information.
      */
     public static final class StopwatchTimer extends Timer {
@@ -902,12 +1033,12 @@
             out.writeLong(mUpdateTime);
         }
 
-        public void plug(long batteryUptime, long batteryRealtime) {
+        public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             if (mNesting > 0) {
                 if (DEBUG && mType < 0) {
                     Log.v(TAG, "old mUpdateTime=" + mUpdateTime);
                 }
-                super.plug(batteryUptime, batteryRealtime);
+                super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
                 mUpdateTime = batteryRealtime;
                 if (DEBUG && mType < 0) {
                     Log.v(TAG, "new mUpdateTime=" + mUpdateTime);
@@ -1443,7 +1574,7 @@
         mHistoryOverflow = false;
     }
 
-    public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
+    public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
         NetworkStats.Entry entry = null;
 
         // Track UID data usage
@@ -1462,7 +1593,7 @@
         }
 
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
-            mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
+            mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime);
         }
 
         // Track both mobile and total overall data
@@ -1483,7 +1614,7 @@
         mBluetoothPingCount = 0;
     }
 
-    public void doPlugLocked(long batteryUptime, long batteryRealtime) {
+    public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
         NetworkStats.Entry entry = null;
 
         for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
@@ -1498,7 +1629,7 @@
             }
         }
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
-            mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
+            mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime);
         }
 
         // Track both mobile and total overall data
@@ -2109,6 +2240,14 @@
         getUidStatsLocked(uid).noteVideoTurnedOffLocked();
     }
 
+    public void noteVibratorOnLocked(int uid, long durationMillis) {
+        getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
+    }
+
+    public void noteVibratorOffLocked(int uid) {
+        getUidStatsLocked(uid).noteVibratorOffLocked();
+    }
+
     public void noteWifiRunningLocked(WorkSource ws) {
         if (!mGlobalWifiRunning) {
             mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
@@ -2402,6 +2541,8 @@
         boolean mVideoTurnedOn;
         StopwatchTimer mVideoTurnedOnTimer;
 
+        BatchTimer mVibratorOnTimer;
+
         Counter[] mUserActivityCounters;
 
         /**
@@ -2439,10 +2580,6 @@
                     mWifiScanTimers, mUnpluggables);
             mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                     mWifiMulticastTimers, mUnpluggables);
-            mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
-                    null, mUnpluggables);
-            mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
-                    null, mUnpluggables);
         }
 
         @Override
@@ -2587,15 +2724,19 @@
             }
         }
 
+        public StopwatchTimer createAudioTurnedOnTimerLocked() {
+            if (mAudioTurnedOnTimer == null) {
+                mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+                        null, mUnpluggables);
+            }
+            return mAudioTurnedOnTimer;
+        }
+
         @Override
         public void noteAudioTurnedOnLocked() {
             if (!mAudioTurnedOn) {
                 mAudioTurnedOn = true;
-                if (mAudioTurnedOnTimer == null) {
-                    mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
-                            null, mUnpluggables);
-                }
-                mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+                createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
             }
         }
 
@@ -2603,19 +2744,25 @@
         public void noteAudioTurnedOffLocked() {
             if (mAudioTurnedOn) {
                 mAudioTurnedOn = false;
-                mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+                if (mAudioTurnedOnTimer != null) {
+                    mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+                }
             }
         }
 
+        public StopwatchTimer createVideoTurnedOnTimerLocked() {
+            if (mVideoTurnedOnTimer == null) {
+                mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+                        null, mUnpluggables);
+            }
+            return mVideoTurnedOnTimer;
+        }
+
         @Override
         public void noteVideoTurnedOnLocked() {
             if (!mVideoTurnedOn) {
                 mVideoTurnedOn = true;
-                if (mVideoTurnedOnTimer == null) {
-                    mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
-                            null, mUnpluggables);
-                }
-                mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+                createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
             }
         }
 
@@ -2623,7 +2770,27 @@
         public void noteVideoTurnedOffLocked() {
             if (mVideoTurnedOn) {
                 mVideoTurnedOn = false;
-                mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+                if (mVideoTurnedOnTimer != null) {
+                    mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+                }
+            }
+        }
+
+        public BatchTimer createVibratorOnTimerLocked() {
+            if (mVibratorOnTimer == null) {
+                mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
+                        mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal);
+            }
+            return mVibratorOnTimer;
+        }
+
+        public void noteVibratorOnLocked(long durationMillis) {
+            createVibratorOnTimerLocked().addDuration(BatteryStatsImpl.this, durationMillis);
+        }
+
+        public void noteVibratorOffLocked() {
+            if (mVibratorOnTimer != null) {
+                mVibratorOnTimer.abortLastDuration(BatteryStatsImpl.this);
             }
         }
 
@@ -2677,6 +2844,11 @@
         }
 
         @Override
+        public Timer getVibratorOnTimer() {
+            return mVibratorOnTimer;
+        }
+
+        @Override
         public void noteUserActivityLocked(int type) {
             if (mUserActivityCounters == null) {
                 initUserActivityLocked();
@@ -2747,6 +2919,14 @@
                 active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
                 active |= mVideoTurnedOn;
             }
+            if (mVibratorOnTimer != null) {
+                if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) {
+                    mVibratorOnTimer.detach();
+                    mVibratorOnTimer = null;
+                } else {
+                    active = true;
+                }
+            }
 
             mLoadedTcpBytesReceived = mLoadedTcpBytesSent = 0;
             mCurrentTcpBytesReceived = mCurrentTcpBytesSent = 0;
@@ -2832,9 +3012,11 @@
                 }
                 if (mAudioTurnedOnTimer != null) {
                     mAudioTurnedOnTimer.detach();
+                    mAudioTurnedOnTimer = null;
                 }
                 if (mVideoTurnedOnTimer != null) {
                     mVideoTurnedOnTimer.detach();
+                    mVideoTurnedOnTimer = null;
                 }
                 if (mUserActivityCounters != null) {
                     for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -2917,6 +3099,12 @@
             } else {
                 out.writeInt(0);
             }
+            if (mVibratorOnTimer != null) {
+                out.writeInt(1);
+                mVibratorOnTimer.writeToParcel(out, batteryRealtime);
+            } else {
+                out.writeInt(0);
+            }
             if (mUserActivityCounters != null) {
                 out.writeInt(1);
                 for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -3016,6 +3204,12 @@
                 mVideoTurnedOnTimer = null;
             }
             if (in.readInt() != 0) {
+                mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
+                        mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in);
+            } else {
+                mVibratorOnTimer = null;
+            }
+            if (in.readInt() != 0) {
                 mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
                 for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
                     mUserActivityCounters[i] = new Counter(mUnpluggables, in);
@@ -3256,14 +3450,14 @@
                 mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
             }
 
-            public void unplug(long batteryUptime, long batteryRealtime) {
+            public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
                 mUnpluggedUserTime = mUserTime;
                 mUnpluggedSystemTime = mSystemTime;
                 mUnpluggedStarts = mStarts;
                 mUnpluggedForegroundTime = mForegroundTime;
             }
 
-            public void plug(long batteryUptime, long batteryRealtime) {
+            public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             }
 
             void detach() {
@@ -3550,11 +3744,11 @@
                 mUnpluggables.add(this);
             }
 
-            public void unplug(long batteryUptime, long batteryRealtime) {
+            public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
                 mUnpluggedWakeups = mWakeups;
             }
 
-            public void plug(long batteryUptime, long batteryRealtime) {
+            public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
             }
 
             void detach() {
@@ -3712,13 +3906,13 @@
                     mUnpluggables.add(this);
                 }
 
-                public void unplug(long batteryUptime, long batteryRealtime) {
+                public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
                     mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
                     mUnpluggedStarts = mStarts;
                     mUnpluggedLaunches = mLaunches;
                 }
 
-                public void plug(long batteryUptime, long batteryRealtime) {
+                public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
                 }
 
                 void detach() {
@@ -4367,7 +4561,7 @@
             }
             mDischargeAmountScreenOn = 0;
             mDischargeAmountScreenOff = 0;
-            doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+            doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
         } else {
             updateKernelWakelocksLocked();
             mHistoryCur.batteryLevel = (byte)level;
@@ -4383,7 +4577,7 @@
                 mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
             }
             updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
-            doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+            doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
         }
         if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
             if (mFile != null) {
@@ -5161,11 +5355,14 @@
             }
             u.mAudioTurnedOn = false;
             if (in.readInt() != 0) {
-                u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in);
+                u.createAudioTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
             }
             u.mVideoTurnedOn = false;
             if (in.readInt() != 0) {
-                u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in);
+                u.createVideoTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+            }
+            if (in.readInt() != 0) {
+                u.createVibratorOnTimerLocked().readSummaryFromParcelLocked(in);
             }
 
             if (in.readInt() != 0) {
@@ -5367,6 +5564,12 @@
             } else {
                 out.writeInt(0);
             }
+            if (u.mVibratorOnTimer != null) {
+                out.writeInt(1);
+                u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+            } else {
+                out.writeInt(0);
+            }
 
             if (u.mUserActivityCounters == null) {
                 out.writeInt(0);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9822e63..e357255 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1327,21 +1327,6 @@
         android:label="@string/permlab_writeGservices"
         android:description="@string/permdesc_writeGservices" />
 
-    <!-- @hide Change the screen compatibility mode of applications -->
-    <permission android:name="android.permission.SET_SCREEN_COMPATIBILITY"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="signature"
-        android:label="@string/permlab_setScreenCompatibility"
-        android:description="@string/permdesc_setScreenCompatibility" />
-
-    <!-- Allows an application to modify the current configuration, such
-         as locale. -->
-    <permission android:name="android.permission.CHANGE_CONFIGURATION"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="system|signature"
-        android:label="@string/permlab_changeConfiguration"
-        android:description="@string/permdesc_changeConfiguration" />
-
     <!-- Allows an application to call
         {@link android.app.ActivityManager#forceStopPackage}.
         @hide -->
@@ -1621,6 +1606,13 @@
         android:description="@string/permdesc_updateBatteryStats"
         android:protectionLevel="signature|system" />
 
+    <!-- Allows an application to update application operation statistics. Not for
+         use by third party apps. @hide -->
+    <permission android:name="android.permission.UPDATE_APP_OPS_STATS"
+        android:label="@string/permlab_updateAppOpsStats"
+        android:description="@string/permdesc_updateAppOpsStats"
+        android:protectionLevel="signature|system" />
+
     <!-- Allows an application to open windows that are for use by parts
          of the system user interface.  Not for use by third party apps. -->
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cb8d0e5..6a93860 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -842,6 +842,12 @@
     <string name="permdesc_updateBatteryStats">Allows the app to modify
         collected battery statistics. Not for use by normal apps.</string>
 
+    <!-- [CHAR LIMIT=NONE] Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_updateAppOpsStats">modify app ops statistics</string>
+    <!-- [CHAR LIMIT=NONE] Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_updateAppOpsStats">Allows the app to modify
+        collected application operation statistics. Not for use by normal apps.</string>
+    
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_backup">control system backup and restore</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index f663e0a..c353ec6 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -47,7 +47,7 @@
 
     Location getLastLocation(in LocationRequest request, String packageName);
 
-    boolean addGpsStatusListener(IGpsStatusListener listener);
+    boolean addGpsStatusListener(IGpsStatusListener listener, String packageName);
     void removeGpsStatusListener(IGpsStatusListener listener);
 
     boolean geocoderIsPresent();
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 5a2f71b..0b9286e 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1461,7 +1461,7 @@
         }
         try {
             GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);
-            result = mService.addGpsStatusListener(transport);
+            result = mService.addGpsStatusListener(transport, mContext.getPackageName());
             if (result) {
                 mGpsStatusListeners.put(listener, transport);
             }
@@ -1507,7 +1507,7 @@
         }
         try {
             GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);
-            result = mService.addGpsStatusListener(transport);
+            result = mService.addGpsStatusListener(transport, mContext.getPackageName());
             if (result) {
                 mNmeaListeners.put(listener, transport);
             }
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);
                 }
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 2eba4e1..5ee52de 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -81,6 +81,13 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public int getPackageUid(String packageName, int userHandle)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public PermissionInfo getPermissionInfo(String name, int flags)
     throws NameNotFoundException {