More work on App Ops service.

Implemented reading and writing state to retain information
across boots, API to retrieve state from it, improved location
manager interaction to monitor both coarse and fine access
and only note operations when location data is being delivered
back to app (not when it is just registering to get the data at
some time in the future).

Also implement tracking of read/write ops on contacts and the
call log.  This involved tweaking the content provider protocol
to pass over the name of the calling package, and some
infrastructure in the ContentProvider transport to note incoming
calls with the app ops service.  The contacts provider and call
log provider turn this on for themselves.

This also implements some of the mechanics of being able to ignore
incoming provider calls...  all that is left are some new APIs for
the real content provider implementation to be involved with
providing the correct behavior for query() (return an empty
cursor with the right columns) and insert() (need to figure out
what URI to return).

Change-Id: I36ebbcd63dee58264a480f3d3786891ca7cbdb4c
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
new file mode 100644
index 0000000..4b97a15
--- /dev/null
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2013, 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;
+
+parcelable AppOpsManager.PackageOps;
+parcelable AppOpsManager.OpEntry;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7210df4..4cea6a0 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -18,7 +18,12 @@
 
 import com.android.internal.app.IAppOpsService;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 
@@ -31,24 +36,177 @@
     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 final int OP_COARSE_LOCATION = 0;
+    public static final int OP_FINE_LOCATION = 1;
+    public static final int OP_GPS = 2;
+    public static final int OP_VIBRATE = 3;
+    public static final int OP_READ_CONTACTS = 4;
+    public static final int OP_WRITE_CONTACTS = 5;
+    public static final int OP_READ_CALL_LOG = 6;
+    public static final int OP_WRITE_CALL_LOG = 7;
 
     public static String opToString(int op) {
         switch (op) {
-            case OP_LOCATION: return "LOCATION";
+            case OP_COARSE_LOCATION: return "COARSE_LOCATION";
+            case OP_FINE_LOCATION: return "FINE_LOCATION";
             case OP_GPS: return "GPS";
             case OP_VIBRATE: return "VIBRATE";
+            case OP_READ_CONTACTS: return "READ_CONTACTS";
+            case OP_WRITE_CONTACTS: return "WRITE_CONTACTS";
+            case OP_READ_CALL_LOG: return "READ_CALL_LOG";
+            case OP_WRITE_CALL_LOG: return "WRITE_CALL_LOG";
             default: return "Unknown(" + op + ")";
         }
     }
 
+    public static class PackageOps implements Parcelable {
+        private final String mPackageName;
+        private final int mUid;
+        private final List<OpEntry> mEntries;
+
+        public PackageOps(String packageName, int uid, List<OpEntry> entries) {
+            mPackageName = packageName;
+            mUid = uid;
+            mEntries = entries;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public List<OpEntry> getOps() {
+            return mEntries;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mPackageName);
+            dest.writeInt(mUid);
+            dest.writeInt(mEntries.size());
+            for (int i=0; i<mEntries.size(); i++) {
+                mEntries.get(i).writeToParcel(dest, flags);
+            }
+        }
+
+        PackageOps(Parcel source) {
+            mPackageName = source.readString();
+            mUid = source.readInt();
+            mEntries = new ArrayList<OpEntry>();
+            final int N = source.readInt();
+            for (int i=0; i<N; i++) {
+                mEntries.add(OpEntry.CREATOR.createFromParcel(source));
+            }
+        }
+
+        public static final Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
+            @Override public PackageOps createFromParcel(Parcel source) {
+                return new PackageOps(source);
+            }
+
+            @Override public PackageOps[] newArray(int size) {
+                return new PackageOps[size];
+            }
+        };
+    }
+
+    public static class OpEntry implements Parcelable {
+        private final int mOp;
+        private final long mTime;
+        private final int mDuration;
+
+        public OpEntry(int op, long time, int duration) {
+            mOp = op;
+            mTime = time;
+            mDuration = duration;
+        }
+
+        public int getOp() {
+            return mOp;
+        }
+
+        public long getTime() {
+            return mTime;
+        }
+
+        public boolean isRunning() {
+            return mDuration == -1;
+        }
+
+        public int getDuration() {
+            return mDuration == -1 ? (int)(System.currentTimeMillis()-mTime) : mDuration;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mOp);
+            dest.writeLong(mTime);
+            dest.writeInt(mDuration);
+        }
+
+        OpEntry(Parcel source) {
+            mOp = source.readInt();
+            mTime = source.readLong();
+            mDuration = source.readInt();
+        }
+
+        public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
+            @Override public OpEntry createFromParcel(Parcel source) {
+                return new OpEntry(source);
+            }
+
+            @Override public OpEntry[] newArray(int size) {
+                return new OpEntry[size];
+            }
+        };
+    }
+
     public AppOpsManager(Context context, IAppOpsService service) {
         mContext = context;
         mService = service;
     }
 
+    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+        try {
+            return mService.getPackagesForOps(ops);
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    public int checkOp(int op, int uid, String packageName) {
+        try {
+            int mode = mService.checkOperation(op, uid, packageName);
+            if (mode == MODE_ERRORED) {
+                throw new SecurityException("Operation not allowed");
+            }
+            return mode;
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
+    public int checkOpNoThrow(int op, int uid, String packageName) {
+        try {
+            return mService.checkOperation(op, uid, packageName);
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
     public int noteOp(int op, int uid, String packageName) {
         try {
             int mode = mService.noteOperation(op, uid, packageName);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 99ac0d6..1d394e8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -623,7 +623,9 @@
         if (mPackageInfo != null) {
             return mPackageInfo.getPackageName();
         }
-        throw new RuntimeException("Not supported in system context");
+        // No mPackageInfo means this is a Context for the system itself,
+        // and this here is its name.
+        return "android";
     }
 
     @Override
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c1411b0..a6f7abc 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
@@ -172,6 +173,10 @@
      * @hide
      */
     class Transport extends ContentProviderNative {
+        AppOpsManager mAppOpsManager = null;
+        int mReadOp = -1;
+        int mWriteOp = -1;
+
         ContentProvider getContentProvider() {
             return ContentProvider.this;
         }
@@ -182,10 +187,11 @@
         }
 
         @Override
-        public Cursor query(Uri uri, String[] projection,
+        public Cursor query(String callingPkg, Uri uri, String[] projection,
                 String selection, String[] selectionArgs, String sortOrder,
                 ICancellationSignal cancellationSignal) {
-            enforceReadPermission(uri);
+            // XXX need content provider to help return correct result.
+            enforceReadPermission(callingPkg, uri);
             return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder,
                     CancellationSignal.fromTransport(cancellationSignal));
         }
@@ -196,63 +202,75 @@
         }
 
         @Override
-        public Uri insert(Uri uri, ContentValues initialValues) {
-            enforceWritePermission(uri);
+        public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
+            // XXX need content provider to help return correct result.
+            enforceWritePermission(callingPkg, uri);
             return ContentProvider.this.insert(uri, initialValues);
         }
 
         @Override
-        public int bulkInsert(Uri uri, ContentValues[] initialValues) {
-            enforceWritePermission(uri);
+        public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
+            if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                return 0;
+            }
             return ContentProvider.this.bulkInsert(uri, initialValues);
         }
 
         @Override
-        public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+        public ContentProviderResult[] applyBatch(String callingPkg,
+                ArrayList<ContentProviderOperation> operations)
                 throws OperationApplicationException {
             for (ContentProviderOperation operation : operations) {
                 if (operation.isReadOperation()) {
-                    enforceReadPermission(operation.getUri());
+                    if (enforceReadPermission(callingPkg, operation.getUri())
+                            != AppOpsManager.MODE_ALLOWED) {
+                        throw new OperationApplicationException("App op not allowed", 0);
+                    }
                 }
 
                 if (operation.isWriteOperation()) {
-                    enforceWritePermission(operation.getUri());
+                    if (enforceWritePermission(callingPkg, operation.getUri())
+                            != AppOpsManager.MODE_ALLOWED) {
+                        throw new OperationApplicationException("App op not allowed", 0);
+                    }
                 }
             }
             return ContentProvider.this.applyBatch(operations);
         }
 
         @Override
-        public int delete(Uri uri, String selection, String[] selectionArgs) {
-            enforceWritePermission(uri);
+        public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
+            if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                return 0;
+            }
             return ContentProvider.this.delete(uri, selection, selectionArgs);
         }
 
         @Override
-        public int update(Uri uri, ContentValues values, String selection,
+        public int update(String callingPkg, Uri uri, ContentValues values, String selection,
                 String[] selectionArgs) {
-            enforceWritePermission(uri);
+            if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                return 0;
+            }
             return ContentProvider.this.update(uri, values, selection, selectionArgs);
         }
 
         @Override
-        public ParcelFileDescriptor openFile(Uri uri, String mode)
+        public ParcelFileDescriptor openFile(String callingPkg, Uri uri, String mode)
                 throws FileNotFoundException {
-            if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
-            else enforceReadPermission(uri);
+            enforceFilePermission(callingPkg, uri, mode);
             return ContentProvider.this.openFile(uri, mode);
         }
 
         @Override
-        public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+        public AssetFileDescriptor openAssetFile(String callingPkg, Uri uri, String mode)
                 throws FileNotFoundException {
-            if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
-            else enforceReadPermission(uri);
+            enforceFilePermission(callingPkg, uri, mode);
             return ContentProvider.this.openAssetFile(uri, mode);
         }
 
         @Override
-        public Bundle call(String method, String arg, Bundle extras) {
+        public Bundle call(String callingPkg, String method, String arg, Bundle extras) {
             return ContentProvider.this.call(method, arg, extras);
         }
 
@@ -262,9 +280,9 @@
         }
 
         @Override
-        public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
-                throws FileNotFoundException {
-            enforceReadPermission(uri);
+        public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
+                Bundle opts) throws FileNotFoundException {
+            enforceFilePermission(callingPkg, uri, "r");
             return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
         }
 
@@ -273,7 +291,28 @@
             return CancellationSignal.createTransport();
         }
 
-        private void enforceReadPermission(Uri uri) throws SecurityException {
+        private void enforceFilePermission(String callingPkg, Uri uri, String mode)
+                throws FileNotFoundException, SecurityException {
+            if (mode != null && mode.startsWith("rw")) {
+                if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                    throw new FileNotFoundException("App op not allowed");
+                }
+            } else {
+                if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                    throw new FileNotFoundException("App op not allowed");
+                }
+            }
+        }
+
+        private int enforceReadPermission(String callingPkg, Uri uri) throws SecurityException {
+            enforceReadPermissionInner(uri);
+            if (mAppOpsManager != null) {
+                return mAppOpsManager.noteOp(mReadOp, Binder.getCallingUid(), callingPkg);
+            }
+            return AppOpsManager.MODE_ALLOWED;
+        }
+
+        private void enforceReadPermissionInner(Uri uri) throws SecurityException {
             final Context context = getContext();
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -334,7 +373,15 @@
                     + ", uid=" + uid + failReason);
         }
 
-        private void enforceWritePermission(Uri uri) throws SecurityException {
+        private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException {
+            enforceWritePermissionInner(uri);
+            if (mAppOpsManager != null) {
+                return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg);
+            }
+            return AppOpsManager.MODE_ALLOWED;
+        }
+
+        private void enforceWritePermissionInner(Uri uri) throws SecurityException {
             final Context context = getContext();
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -471,6 +518,14 @@
         return mPathPermissions;
     }
 
+    /** @hide */
+    public final void setAppOps(int readOp, int writeOp) {
+        mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService(
+                Context.APP_OPS_SERVICE);
+        mTransport.mReadOp = readOp;
+        mTransport.mWriteOp = writeOp;
+    }
+
     /**
      * Implement this to initialize your content provider on startup.
      * This method is called for all registered content providers on the
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 204f963..8dffac7 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -45,6 +45,7 @@
 public class ContentProviderClient {
     private final IContentProvider mContentProvider;
     private final ContentResolver mContentResolver;
+    private final String mPackageName;
     private final boolean mStable;
     private boolean mReleased;
 
@@ -55,6 +56,7 @@
             IContentProvider contentProvider, boolean stable) {
         mContentProvider = contentProvider;
         mContentResolver = contentResolver;
+        mPackageName = contentResolver.mPackageName;
         mStable = stable;
     }
 
@@ -81,8 +83,8 @@
             cancellationSignal.setRemote(remoteCancellationSignal);
         }
         try {
-            return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder,
-                    remoteCancellationSignal);
+            return mContentProvider.query(mPackageName, url, projection, selection,  selectionArgs,
+                    sortOrder, remoteCancellationSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -119,7 +121,7 @@
     public Uri insert(Uri url, ContentValues initialValues)
             throws RemoteException {
         try {
-            return mContentProvider.insert(url, initialValues);
+            return mContentProvider.insert(mPackageName, url, initialValues);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -131,7 +133,7 @@
     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
         try {
-            return mContentProvider.bulkInsert(url, initialValues);
+            return mContentProvider.bulkInsert(mPackageName, url, initialValues);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -144,7 +146,7 @@
     public int delete(Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
         try {
-            return mContentProvider.delete(url, selection, selectionArgs);
+            return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -157,7 +159,7 @@
     public int update(Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
         try {
-            return mContentProvider.update(url, values, selection, selectionArgs);
+            return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -176,7 +178,7 @@
     public ParcelFileDescriptor openFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         try {
-            return mContentProvider.openFile(url, mode);
+            return mContentProvider.openFile(mPackageName, url, mode);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -195,7 +197,7 @@
     public AssetFileDescriptor openAssetFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         try {
-            return mContentProvider.openAssetFile(url, mode);
+            return mContentProvider.openAssetFile(mPackageName, url, mode);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -209,7 +211,7 @@
             String mimeType, Bundle opts)
             throws RemoteException, FileNotFoundException {
         try {
-            return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+            return mContentProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -222,7 +224,7 @@
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
         try {
-            return mContentProvider.applyBatch(operations);
+            return mContentProvider.applyBatch(mPackageName, operations);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -235,7 +237,7 @@
     public Bundle call(String method, String arg, Bundle extras)
             throws RemoteException {
         try {
-            return mContentProvider.call(method, arg, extras);
+            return mContentProvider.call(mPackageName, method, arg, extras);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 384ba57..6f822c1 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -81,6 +81,7 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
 
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
 
                     // String[] projection
@@ -110,8 +111,8 @@
                     ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
                             data.readStrongBinder());
 
-                    Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder,
-                            cancellationSignal);
+                    Cursor cursor = query(callingPkg, url, projection, selection, selectionArgs,
+                            sortOrder, cancellationSignal);
                     if (cursor != null) {
                         try {
                             CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
@@ -150,10 +151,11 @@
                 case INSERT_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     ContentValues values = ContentValues.CREATOR.createFromParcel(data);
 
-                    Uri out = insert(url, values);
+                    Uri out = insert(callingPkg, url, values);
                     reply.writeNoException();
                     Uri.writeToParcel(reply, out);
                     return true;
@@ -162,10 +164,11 @@
                 case BULK_INSERT_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
 
-                    int count = bulkInsert(url, values);
+                    int count = bulkInsert(callingPkg, url, values);
                     reply.writeNoException();
                     reply.writeInt(count);
                     return true;
@@ -174,13 +177,14 @@
                 case APPLY_BATCH_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     final int numOperations = data.readInt();
                     final ArrayList<ContentProviderOperation> operations =
                             new ArrayList<ContentProviderOperation>(numOperations);
                     for (int i = 0; i < numOperations; i++) {
                         operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
                     }
-                    final ContentProviderResult[] results = applyBatch(operations);
+                    final ContentProviderResult[] results = applyBatch(callingPkg, operations);
                     reply.writeNoException();
                     reply.writeTypedArray(results, 0);
                     return true;
@@ -189,11 +193,12 @@
                 case DELETE_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String selection = data.readString();
                     String[] selectionArgs = data.readStringArray();
 
-                    int count = delete(url, selection, selectionArgs);
+                    int count = delete(callingPkg, url, selection, selectionArgs);
 
                     reply.writeNoException();
                     reply.writeInt(count);
@@ -203,12 +208,13 @@
                 case UPDATE_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     ContentValues values = ContentValues.CREATOR.createFromParcel(data);
                     String selection = data.readString();
                     String[] selectionArgs = data.readStringArray();
 
-                    int count = update(url, values, selection, selectionArgs);
+                    int count = update(callingPkg, url, values, selection, selectionArgs);
 
                     reply.writeNoException();
                     reply.writeInt(count);
@@ -218,11 +224,12 @@
                 case OPEN_FILE_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String mode = data.readString();
 
                     ParcelFileDescriptor fd;
-                    fd = openFile(url, mode);
+                    fd = openFile(callingPkg, url, mode);
                     reply.writeNoException();
                     if (fd != null) {
                         reply.writeInt(1);
@@ -237,11 +244,12 @@
                 case OPEN_ASSET_FILE_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String mode = data.readString();
 
                     AssetFileDescriptor fd;
-                    fd = openAssetFile(url, mode);
+                    fd = openAssetFile(callingPkg, url, mode);
                     reply.writeNoException();
                     if (fd != null) {
                         reply.writeInt(1);
@@ -257,11 +265,12 @@
                 {
                     data.enforceInterface(IContentProvider.descriptor);
 
+                    String callingPkg = data.readString();
                     String method = data.readString();
                     String stringArg = data.readString();
                     Bundle args = data.readBundle();
 
-                    Bundle responseBundle = call(method, stringArg, args);
+                    Bundle responseBundle = call(callingPkg, method, stringArg, args);
 
                     reply.writeNoException();
                     reply.writeBundle(responseBundle);
@@ -283,12 +292,13 @@
                 case OPEN_TYPED_ASSET_FILE_TRANSACTION:
                 {
                     data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
                     Uri url = Uri.CREATOR.createFromParcel(data);
                     String mimeType = data.readString();
                     Bundle opts = data.readBundle();
 
                     AssetFileDescriptor fd;
-                    fd = openTypedAssetFile(url, mimeType, opts);
+                    fd = openTypedAssetFile(callingPkg, url, mimeType, opts);
                     reply.writeNoException();
                     if (fd != null) {
                         reply.writeInt(1);
@@ -337,7 +347,7 @@
         return mRemote;
     }
 
-    public Cursor query(Uri url, String[] projection, String selection,
+    public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
                     throws RemoteException {
         BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
@@ -346,6 +356,7 @@
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             int length = 0;
             if (projection != null) {
@@ -413,13 +424,14 @@
         }
     }
 
-    public Uri insert(Uri url, ContentValues values) throws RemoteException
+    public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             values.writeToParcel(data, 0);
 
@@ -434,12 +446,13 @@
         }
     }
 
-    public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException {
+    public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             data.writeTypedArray(values, 0);
 
@@ -454,12 +467,14 @@
         }
     }
 
-    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
-            throws RemoteException, OperationApplicationException {
+    public ContentProviderResult[] applyBatch(String callingPkg, 
+            ArrayList<ContentProviderOperation> operations)
+                    throws RemoteException, OperationApplicationException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
+            data.writeString(callingPkg);
             data.writeInt(operations.size());
             for (ContentProviderOperation operation : operations) {
                 operation.writeToParcel(data, 0);
@@ -476,13 +491,14 @@
         }
     }
 
-    public int delete(Uri url, String selection, String[] selectionArgs)
+    public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             data.writeString(selection);
             data.writeStringArray(selectionArgs);
@@ -498,13 +514,14 @@
         }
     }
 
-    public int update(Uri url, ContentValues values, String selection,
+    public int update(String callingPkg, Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             values.writeToParcel(data, 0);
             data.writeString(selection);
@@ -521,13 +538,14 @@
         }
     }
 
-    public ParcelFileDescriptor openFile(Uri url, String mode)
+    public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             data.writeString(mode);
 
@@ -543,13 +561,14 @@
         }
     }
 
-    public AssetFileDescriptor openAssetFile(Uri url, String mode)
+    public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
             throws RemoteException, FileNotFoundException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             data.writeString(mode);
 
@@ -566,13 +585,14 @@
         }
     }
 
-    public Bundle call(String method, String request, Bundle args)
+    public Bundle call(String callingPkg, String method, String request, Bundle args)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             data.writeString(method);
             data.writeString(request);
             data.writeBundle(args);
@@ -609,13 +629,14 @@
         }
     }
 
-    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
-            throws RemoteException, FileNotFoundException {
+    public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+            Bundle opts) throws RemoteException, FileNotFoundException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IContentProvider.descriptor);
 
+            data.writeString(callingPkg);
             url.writeToParcel(data, 0);
             data.writeString(mimeType);
             data.writeBundle(opts);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 86bf8c2..51c9aa5 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -207,6 +207,7 @@
 
     public ContentResolver(Context context) {
         mContext = context;
+        mPackageName = context.getPackageName();
     }
 
     /** @hide */
@@ -392,7 +393,7 @@
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
             try {
-                qCursor = unstableProvider.query(uri, projection,
+                qCursor = unstableProvider.query(mPackageName, uri, projection,
                         selection, selectionArgs, sortOrder, remoteCancellationSignal);
             } catch (DeadObjectException e) {
                 // The remote process has died...  but we only hold an unstable
@@ -403,7 +404,7 @@
                 if (stableProvider == null) {
                     return null;
                 }
-                qCursor = stableProvider.query(uri, projection,
+                qCursor = stableProvider.query(mPackageName, uri, projection,
                         selection, selectionArgs, sortOrder, remoteCancellationSignal);
             }
             if (qCursor == null) {
@@ -651,7 +652,7 @@
 
                 try {
                     try {
-                        fd = unstableProvider.openAssetFile(uri, mode);
+                        fd = unstableProvider.openAssetFile(mPackageName, uri, mode);
                         if (fd == null) {
                             // The provider will be released by the finally{} clause
                             return null;
@@ -665,7 +666,7 @@
                         if (stableProvider == null) {
                             throw new FileNotFoundException("No content provider: " + uri);
                         }
-                        fd = stableProvider.openAssetFile(uri, mode);
+                        fd = stableProvider.openAssetFile(mPackageName, uri, mode);
                         if (fd == null) {
                             // The provider will be released by the finally{} clause
                             return null;
@@ -743,7 +744,7 @@
 
         try {
             try {
-                fd = unstableProvider.openTypedAssetFile(uri, mimeType, opts);
+                fd = unstableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
                 if (fd == null) {
                     // The provider will be released by the finally{} clause
                     return null;
@@ -757,7 +758,7 @@
                 if (stableProvider == null) {
                     throw new FileNotFoundException("No content provider: " + uri);
                 }
-                fd = stableProvider.openTypedAssetFile(uri, mimeType, opts);
+                fd = stableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
                 if (fd == null) {
                     // The provider will be released by the finally{} clause
                     return null;
@@ -892,7 +893,7 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            Uri createdRow = provider.insert(url, values);
+            Uri createdRow = provider.insert(mPackageName, url, values);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
             return createdRow;
@@ -953,7 +954,7 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            int rowsCreated = provider.bulkInsert(url, values);
+            int rowsCreated = provider.bulkInsert(mPackageName, url, values);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
             return rowsCreated;
@@ -984,7 +985,7 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            int rowsDeleted = provider.delete(url, where, selectionArgs);
+            int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
             return rowsDeleted;
@@ -1018,7 +1019,7 @@
         }
         try {
             long startTime = SystemClock.uptimeMillis();
-            int rowsUpdated = provider.update(uri, values, where, selectionArgs);
+            int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
             return rowsUpdated;
@@ -1057,7 +1058,7 @@
             throw new IllegalArgumentException("Unknown URI " + uri);
         }
         try {
-            return provider.call(method, arg, extras);
+            return provider.call(mPackageName, method, arg, extras);
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
             // Manager will kill this process shortly anyway.
@@ -1955,7 +1956,13 @@
         return sContentService;
     }
 
+    /** @hide */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
     private static IContentService sContentService;
     private final Context mContext;
+    final String mPackageName;
     private static final String TAG = "ContentResolver";
 }
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index eeba1e0..62b79f0 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -34,30 +34,33 @@
  * @hide
  */
 public interface IContentProvider extends IInterface {
-    public Cursor query(Uri url, String[] projection, String selection,
+    public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
                     throws RemoteException;
     public String getType(Uri url) throws RemoteException;
-    public Uri insert(Uri url, ContentValues initialValues)
+    public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
             throws RemoteException;
-    public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException;
-    public int delete(Uri url, String selection, String[] selectionArgs)
+    public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
             throws RemoteException;
-    public int update(Uri url, ContentValues values, String selection,
+    public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+            throws RemoteException;
+    public int update(String callingPkg, Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException;
-    public ParcelFileDescriptor openFile(Uri url, String mode)
+    public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
             throws RemoteException, FileNotFoundException;
-    public AssetFileDescriptor openAssetFile(Uri url, String mode)
+    public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
             throws RemoteException, FileNotFoundException;
-    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
-            throws RemoteException, OperationApplicationException;
-    public Bundle call(String method, String arg, Bundle extras) throws RemoteException;
+    public ContentProviderResult[] applyBatch(String callingPkg,
+            ArrayList<ContentProviderOperation> operations)
+                    throws RemoteException, OperationApplicationException;
+    public Bundle call(String callingPkg, String method, String arg, Bundle extras)
+            throws RemoteException;
     public ICancellationSignal createCancellationSignal() throws RemoteException;
 
     // Data interchange.
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
-    public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
-            throws RemoteException, FileNotFoundException;
+    public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+            Bundle opts) throws RemoteException, FileNotFoundException;
 
     /* IPC constants */
     static final String descriptor = "android.content.IContentProvider";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 441214c..3d850cf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -456,6 +456,18 @@
             "android.settings.APPLICATION_DETAILS_SETTINGS";
 
     /**
+     * @hide
+     * Activity Action: Show the "app ops" settings screen.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_APP_OPS_SETTINGS =
+            "android.settings.APP_OPS_SETTINGS";
+
+    /**
      * Activity Action: Show settings for system update functionality.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -774,7 +786,7 @@
                 arg.putString(Settings.NameValueTable.VALUE, value);
                 arg.putInt(CALL_METHOD_USER_KEY, userHandle);
                 IContentProvider cp = lazyGetProvider(cr);
-                cp.call(mCallSetCommand, name, arg);
+                cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
             } catch (RemoteException e) {
                 Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
                 return false;
@@ -821,7 +833,7 @@
                         args = new Bundle();
                         args.putInt(CALL_METHOD_USER_KEY, userHandle);
                     }
-                    Bundle b = cp.call(mCallGetCommand, name, args);
+                    Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
                     if (b != null) {
                         String value = b.getPairValue();
                         // Don't update our cache for reads of other users' data
@@ -846,7 +858,7 @@
 
             Cursor c = null;
             try {
-                c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+                c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
                              new String[]{name}, null, null);
                 if (c == null) {
                     Log.w(TAG, "Can't get key " + name + " from " + mUri);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index c934587..827dba6 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -16,10 +16,12 @@
 
 package com.android.internal.app;
 
+import android.app.AppOpsManager;
+
 interface IAppOpsService {
+    List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
+    int checkOperation(int code, int uid, String packageName);
     int noteOperation(int code, int uid, String packageName);
     int startOperation(int code, int uid, String packageName);
     void finishOperation(int code, int uid, String packageName);
-    int noteTimedOperation(int code, int uid, String packageName, int duration);
-    void earlyFinishOperation(int code, int uid, String packageName);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e357255..a69870b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1606,6 +1606,13 @@
         android:description="@string/permdesc_updateBatteryStats"
         android:protectionLevel="signature|system" />
 
+    <!-- @hide Allows an application to collect battery statistics -->
+    <permission android:name="android.permission.GET_APP_OPS_STATS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:label="@string/permlab_getAppOpsStats"
+        android:description="@string/permdesc_getAppOpsStats"
+        android:protectionLevel="signature|system|development" />
+
     <!-- 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"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6a93860..fa26089 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -843,6 +843,12 @@
         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_getAppOpsStats">retrieve 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_getAppOpsStats">Allows the app to retrieve
+        collected application operation 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