Merge "Work on issue #10130785: Restore silence and vibrate settings..." into klp-dev
diff --git a/api/current.txt b/api/current.txt
index 7a00fb9..c7b3ed9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5623,6 +5623,7 @@
     method public void attachInfo(android.content.Context, android.content.pm.ProviderInfo);
     method public int bulkInsert(android.net.Uri, android.content.ContentValues[]);
     method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle);
+    method public android.net.Uri canonicalize(android.net.Uri);
     method public abstract int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public final java.lang.String getCallingPackage();
@@ -5652,6 +5653,7 @@
     method protected final void setReadPermission(java.lang.String);
     method protected final void setWritePermission(java.lang.String);
     method public void shutdown();
+    method public android.net.Uri uncanonicalize(android.net.Uri);
     method public abstract int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
   }
 
@@ -5663,6 +5665,7 @@
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
     method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
     method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
+    method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
     method public android.content.ContentProvider getLocalContentProvider();
     method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -5677,6 +5680,7 @@
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException;
     method public boolean release();
+    method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException;
     method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
   }
 
@@ -5742,6 +5746,7 @@
     method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle);
     method public deprecated void cancelSync(android.net.Uri);
     method public static void cancelSync(android.accounts.Account, java.lang.String);
+    method public final android.net.Uri canonicalize(android.net.Uri);
     method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
     method public static deprecated android.content.SyncInfo getCurrentSync();
     method public static java.util.List<android.content.SyncInfo> getCurrentSyncs();
@@ -5779,6 +5784,7 @@
     method public static void setMasterSyncAutomatically(boolean);
     method public static void setSyncAutomatically(android.accounts.Account, java.lang.String, boolean);
     method public deprecated void startSync(android.net.Uri, android.os.Bundle);
+    method public final android.net.Uri uncanonicalize(android.net.Uri);
     method public final void unregisterContentObserver(android.database.ContentObserver);
     method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
     method public static void validateSyncExtrasBundle(android.os.Bundle);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 24c396a..65a3a07 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -348,10 +348,36 @@
         }
 
         @Override
-        public ICancellationSignal createCancellationSignal() throws RemoteException {
+        public ICancellationSignal createCancellationSignal() {
             return CancellationSignal.createTransport();
         }
 
+        @Override
+        public Uri canonicalize(String callingPkg, Uri uri) {
+            if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                return null;
+            }
+            mCallingPackage.set(callingPkg);
+            try {
+                return ContentProvider.this.canonicalize(uri);
+            } finally {
+                mCallingPackage.set(null);
+            }
+        }
+
+        @Override
+        public Uri uncanonicalize(String callingPkg, Uri uri) {
+            if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+                return null;
+            }
+            mCallingPackage.set(callingPkg);
+            try {
+                return ContentProvider.this.uncanonicalize(uri);
+            } finally {
+                mCallingPackage.set(null);
+            }
+        }
+
         private void enforceFilePermission(String callingPkg, Uri uri, String mode)
                 throws FileNotFoundException, SecurityException {
             if (mode != null && mode.indexOf('w') != -1) {
@@ -841,6 +867,56 @@
     public abstract String getType(Uri uri);
 
     /**
+     * Implement this to support canonicalization of URIs that refer to your
+     * content provider.  A canonical URI is one that can be transported across
+     * devices, backup/restore, and other contexts, and still be able to refer
+     * to the same data item.  Typically this is implemented by adding query
+     * params to the URI allowing the content provider to verify that an incoming
+     * canonical URI references the same data as it was originally intended for and,
+     * if it doesn't, to find that data (if it exists) in the current environment.
+     *
+     * <p>For example, if the content provider holds people and a normal URI in it
+     * is created with a row index into that people database, the cananical representation
+     * may have an additional query param at the end which specifies the name of the
+     * person it is intended for.  Later calls into the provider with that URI will look
+     * up the row of that URI's base index and, if it doesn't match or its entry's
+     * name doesn't match the name in the query param, perform a query on its database
+     * to find the correct row to operate on.</p>
+     *
+     * <p>If you implement support for canonical URIs, <b>all</b> incoming calls with
+     * URIs (including this one) must perform this verification and recovery of any
+     * canonical URIs they receive.  In addition, you must also implement
+     * {@link #uncanonicalize} to strip the canonicalization of any of these URIs.</p>
+     *
+     * <p>The default implementation of this method returns null, indicating that
+     * canonical URIs are not supported.</p>
+     *
+     * @param url The Uri to canonicalize.
+     *
+     * @return Return the canonical representation of <var>url</var>, or null if
+     * canonicalization of that Uri is not supported.
+     */
+    public Uri canonicalize(Uri url) {
+        return null;
+    }
+
+    /**
+     * Remove canonicalization from canonical URIs previously returned by
+     * {@link #canonicalize}.  For example, if your implementation is to add
+     * a query param to canonicalize a URI, this method can simply trip any
+     * query params on the URI.  The default implementation always returns the
+     * same <var>url</var> that was passed in.
+     *
+     * @param url The Uri to remove any canonicalization from.
+     *
+     * @return Return the non-canonical representation of <var>url</var>, or return
+     * the <var>url</var> as-is if there is nothing to do.  Never return null.
+     */
+    public Uri uncanonicalize(Uri url) {
+        return url;
+    }
+
+    /**
      * @hide
      * Implementation when a caller has performed an insert on the content
      * provider, but that call has been rejected for the operation given
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 4e8dd82..e6d9b24 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -110,6 +110,30 @@
         }
     }
 
+    /** See {@link ContentProvider#canonicalize} */
+    public final Uri canonicalize(Uri url) throws RemoteException {
+        try {
+            return mContentProvider.canonicalize(mPackageName, url);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
+    }
+
+    /** See {@link ContentProvider#uncanonicalize} */
+    public final Uri uncanonicalize(Uri url) throws RemoteException {
+        try {
+            return mContentProvider.uncanonicalize(mPackageName, url);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
+    }
+
     /** See {@link ContentProvider#insert ContentProvider.insert} */
     public Uri insert(Uri url, ContentValues initialValues)
             throws RemoteException {
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 744e68c..bcf0b63 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -323,6 +323,30 @@
                     reply.writeStrongBinder(cancellationSignal.asBinder());
                     return true;
                 }
+
+                case CANONICALIZE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+
+                    Uri out = canonicalize(callingPkg, url);
+                    reply.writeNoException();
+                    Uri.writeToParcel(reply, out);
+                    return true;
+                }
+
+                case UNCANONICALIZE_TRANSACTION:
+                {
+                    data.enforceInterface(IContentProvider.descriptor);
+                    String callingPkg = data.readString();
+                    Uri url = Uri.CREATOR.createFromParcel(data);
+
+                    Uri out = uncanonicalize(callingPkg, url);
+                    reply.writeNoException();
+                    Uri.writeToParcel(reply, out);
+                    return true;
+                }
             }
         } catch (Exception e) {
             DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -685,5 +709,46 @@
         }
     }
 
+    public Uri canonicalize(String callingPkg, Uri url) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        try {
+            data.writeInterfaceToken(IContentProvider.descriptor);
+
+            data.writeString(callingPkg);
+            url.writeToParcel(data, 0);
+
+            mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0);
+
+            DatabaseUtils.readExceptionFromParcel(reply);
+            Uri out = Uri.CREATOR.createFromParcel(reply);
+            return out;
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
+    public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        try {
+            data.writeInterfaceToken(IContentProvider.descriptor);
+
+            data.writeString(callingPkg);
+            url.writeToParcel(data, 0);
+
+            mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0);
+
+            DatabaseUtils.readExceptionFromParcel(reply);
+            Uri out = Uri.CREATOR.createFromParcel(reply);
+            return out;
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 8a5a56c..9f462aa 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -497,6 +497,86 @@
     }
 
     /**
+     * Transform the given <var>url</var> to a canonical representation of
+     * its referenced resource, which can be used across devices, persisted,
+     * backed up and restored, etc.  The returned Uri is still a fully capable
+     * Uri for use with its content provider, allowing you to do all of the
+     * same content provider operations as with the original Uri --
+     * {@link #query}, {@link #openInputStream(android.net.Uri)}, etc.  The
+     * only difference in behavior between the original and new Uris is that
+     * the content provider may need to do some additional work at each call
+     * using it to resolve it to the correct resource, especially if the
+     * canonical Uri has been moved to a different environment.
+     *
+     * <p>If you are moving a canonical Uri between environments, you should
+     * perform another call to {@link #canonicalize} with that original Uri to
+     * re-canonicalize it for the current environment.  Alternatively, you may
+     * want to use {@link #uncanonicalize} to transform it to a non-canonical
+     * Uri that works only in the current environment but potentially more
+     * efficiently than the canonical representation.</p>
+     *
+     * @param url The {@link Uri} that is to be transformed to a canonical
+     * representation.  Like all resolver calls, the input can be either
+     * a non-canonical or canonical Uri.
+     *
+     * @return Returns the official canonical representation of <var>url</var>,
+     * or null if the content provider does not support a canonical representation
+     * of the given Uri.  Many providers may not support canonicalization of some
+     * or all of their Uris.
+     *
+     * @see #uncanonicalize
+     */
+    public final Uri canonicalize(Uri url) {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return null;
+        }
+
+        try {
+            return provider.canonicalize(mPackageName, url);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
+     * Given a canonical Uri previously generated by {@link #canonicalize}, convert
+     * it to its local non-canonical form.  This can be useful in some cases where
+     * you know that you will only be using the Uri in the current environment and
+     * want to avoid any possible overhead when using it with the content
+     * provider.
+     *
+     * @param url The canonical {@link Uri} that is to be convered back to its
+     * non-canonical form.
+     *
+     * @return Returns the non-canonical representation of <var>url</var>.  This
+     * function never returns null; if there is no conversion to be done, it returns
+     * the same Uri that was provided.
+     *
+     * @see #canonicalize
+     */
+    public final Uri uncanonicalize(Uri url) {
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return null;
+        }
+
+        try {
+            return provider.uncanonicalize(mPackageName, url);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
      * Open a stream on to the content associated with a content URI.  If there
      * is no data associated with the URI, FileNotFoundException is thrown.
      *
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 6ea8876..f92a404 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -59,6 +59,9 @@
             throws RemoteException;
     public ICancellationSignal createCancellationSignal() throws RemoteException;
 
+    public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
+    public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;
+
     // Data interchange.
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
     public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
@@ -80,4 +83,6 @@
     static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
     static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
     static final int CREATE_CANCELATION_SIGNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 23;
+    static final int CANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 24;
+    static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25;
 }
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 596ea0a..28d52b0 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -137,6 +137,16 @@
         public ICancellationSignal createCancellationSignal() throws RemoteException {
             return null;
         }
+
+        @Override
+        public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+            return MockContentProvider.this.canonicalize(uri);
+        }
+
+        @Override
+        public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+            return MockContentProvider.this.uncanonicalize(uri);
+        }
     }
     private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
 
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index b14ce49..c0dc7c3 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -114,4 +114,14 @@
     public ICancellationSignal createCancellationSignal() throws RemoteException {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
+
+    @Override
+    public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
 }