diff options
41 files changed, 1495 insertions, 169 deletions
diff --git a/api/current.txt b/api/current.txt index 936eb39..27a8480 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5500,10 +5500,13 @@ package android.content { method public void onLowMemory(); method public void onTrimMemory(int); method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method protected final android.os.ParcelFileDescriptor openFileHelper(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public android.os.ParcelFileDescriptor openPipeHelper(android.net.Uri, java.lang.String, android.os.Bundle, T, android.content.ContentProvider.PipeDataWriter<T>) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; + method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method protected final void setPathPermissions(android.content.pm.PathPermission[]); @@ -5527,8 +5530,11 @@ package android.content { method public java.lang.String getType(android.net.Uri) throws android.os.RemoteException; method public android.net.Uri insert(android.net.Uri, android.content.ContentValues) throws android.os.RemoteException; method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException; + method public android.content.res.AssetFileDescriptor openAssetFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException, android.os.RemoteException; + method public android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException, android.os.RemoteException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; 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(); @@ -5616,11 +5622,14 @@ package android.content { method public void notifyChange(android.net.Uri, android.database.ContentObserver); method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean); method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; + method public final android.os.ParcelFileDescriptor openFileDescriptor(android.net.Uri, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final java.io.InputStream openInputStream(android.net.Uri) throws java.io.FileNotFoundException; method public final java.io.OutputStream openOutputStream(android.net.Uri) throws java.io.FileNotFoundException; method public final java.io.OutputStream openOutputStream(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; + method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver); diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index cf627d7..b9121c7 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -16,11 +16,9 @@ package android.content; -import static android.content.pm.PackageManager.GET_PROVIDERS; 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; import android.content.res.AssetFileDescriptor; @@ -261,17 +259,21 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } @Override - public ParcelFileDescriptor openFile(String callingPkg, Uri uri, String mode) + public ParcelFileDescriptor openFile( + String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { enforceFilePermission(callingPkg, uri, mode); - return ContentProvider.this.openFile(uri, mode); + return ContentProvider.this.openFile( + uri, mode, CancellationSignal.fromTransport(cancellationSignal)); } @Override - public AssetFileDescriptor openAssetFile(String callingPkg, Uri uri, String mode) + public AssetFileDescriptor openAssetFile( + String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal) throws FileNotFoundException { enforceFilePermission(callingPkg, uri, mode); - return ContentProvider.this.openAssetFile(uri, mode); + return ContentProvider.this.openAssetFile( + uri, mode, CancellationSignal.fromTransport(cancellationSignal)); } @Override @@ -286,9 +288,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 { @Override public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType, - Bundle opts) throws FileNotFoundException { + Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException { enforceFilePermission(callingPkg, uri, "r"); - return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts); + return ContentProvider.this.openTypedAssetFile( + uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal)); } @Override @@ -874,6 +877,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * <p>The returned ParcelFileDescriptor is owned by the caller, so it is * their responsibility to close it when done. That is, the implementation * of this method should create a new ParcelFileDescriptor for each call. + * <p> + * If opened with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor can be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" or "rwt" modes implies a file on disk that + * supports seeking. + * <p> + * If you need to detect when the returned ParcelFileDescriptor has been + * closed, or if the remote process has crashed or encountered some other + * error, you can use {@link ParcelFileDescriptor#open(File, int, + * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)}, + * {@link ParcelFileDescriptor#createReliablePipe()}, or + * {@link ParcelFileDescriptor#createReliableSocketPair()}. * * <p class="note">For use in Intents, you will want to implement {@link #getType} * to return the appropriate MIME type for the data returned here with @@ -911,6 +926,74 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** + * Override this to handle requests to open a file blob. + * The default implementation always throws {@link FileNotFoundException}. + * This method can be called from multiple threads, as described in + * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes + * and Threads</a>. + * + * <p>This method returns a ParcelFileDescriptor, which is returned directly + * to the caller. This way large data (such as images and documents) can be + * returned without copying the content. + * + * <p>The returned ParcelFileDescriptor is owned by the caller, so it is + * their responsibility to close it when done. That is, the implementation + * of this method should create a new ParcelFileDescriptor for each call. + * <p> + * If opened with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor can be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" or "rwt" modes implies a file on disk that + * supports seeking. + * <p> + * If you need to detect when the returned ParcelFileDescriptor has been + * closed, or if the remote process has crashed or encountered some other + * error, you can use {@link ParcelFileDescriptor#open(File, int, + * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)}, + * {@link ParcelFileDescriptor#createReliablePipe()}, or + * {@link ParcelFileDescriptor#createReliableSocketPair()}. + * + * <p class="note">For use in Intents, you will want to implement {@link #getType} + * to return the appropriate MIME type for the data returned here with + * the same URI. This will allow intent resolution to automatically determine the data MIME + * type and select the appropriate matching targets as part of its operation.</p> + * + * <p class="note">For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}. + * You may also want to support other common columns if you have additional meta-data + * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED} + * in {@link android.provider.MediaStore.MediaColumns}.</p> + * + * @param uri The URI whose file is to be opened. + * @param mode Access mode for the file. May be "r" for read-only access, + * "w" for write-only access, "rw" for read and write access, or + * "rwt" for read and write access that truncates any existing + * file. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new ParcelFileDescriptor which you can use to access + * the file. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the file. + * + * @see #openAssetFile(Uri, String) + * @see #openFileHelper(Uri, String) + * @see #getType(android.net.Uri) + */ + public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) + throws FileNotFoundException { + return openFile(uri, mode); + } + + /** * This is like {@link #openFile}, but can be implemented by providers * that need to be able to return sub-sections of files, often assets * inside of their .apk. @@ -924,11 +1007,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * {@link ContentResolver#openInputStream ContentResolver.openInputStream} * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream} * methods. + * <p> + * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. * * <p class="note">If you are implementing this to return a full file, you * should create the AssetFileDescriptor with * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with - * applications that can not handle sub-sections of files.</p> + * applications that cannot handle sub-sections of files.</p> * * <p class="note">For use in Intents, you will want to implement {@link #getType} * to return the appropriate MIME type for the data returned here with @@ -965,6 +1051,68 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** + * This is like {@link #openFile}, but can be implemented by providers + * that need to be able to return sub-sections of files, often assets + * inside of their .apk. + * This method can be called from multiple threads, as described in + * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes + * and Threads</a>. + * + * <p>If you implement this, your clients must be able to deal with such + * file slices, either directly with + * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level + * {@link ContentResolver#openInputStream ContentResolver.openInputStream} + * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream} + * methods. + * <p> + * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. + * + * <p class="note">If you are implementing this to return a full file, you + * should create the AssetFileDescriptor with + * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with + * applications that cannot handle sub-sections of files.</p> + * + * <p class="note">For use in Intents, you will want to implement {@link #getType} + * to return the appropriate MIME type for the data returned here with + * the same URI. This will allow intent resolution to automatically determine the data MIME + * type and select the appropriate matching targets as part of its operation.</p> + * + * <p class="note">For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}.</p> + * + * @param uri The URI whose file is to be opened. + * @param mode Access mode for the file. May be "r" for read-only access, + * "w" for write-only access (erasing whatever data is currently in + * the file), "wa" for write-only access to append to any existing data, + * "rw" for read and write access on any existing data, and "rwt" for read + * and write access that truncates any existing file. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new AssetFileDescriptor which you can use to access + * the file. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the file. + * + * @see #openFile(Uri, String) + * @see #openFileHelper(Uri, String) + * @see #getType(android.net.Uri) + */ + public AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal) + throws FileNotFoundException { + return openAssetFile(uri, mode); + } + + /** * Convenience for subclasses that wish to implement {@link #openFile} * by looking up a column named "_data" at the given URI. * @@ -1041,6 +1189,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * * <p>See {@link ClipData} for examples of the use and implementation * of this method. + * <p> + * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. * * <p class="note">For better interoperability with other applications, it is recommended * that for any URIs that can be opened, you also support queries on them @@ -1086,6 +1237,64 @@ public abstract class ContentProvider implements ComponentCallbacks2 { throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter); } + + /** + * Called by a client to open a read-only stream containing data of a + * particular MIME type. This is like {@link #openAssetFile(Uri, String)}, + * except the file can only be read-only and the content provider may + * perform data conversions to generate data of the desired type. + * + * <p>The default implementation compares the given mimeType against the + * result of {@link #getType(Uri)} and, if they match, simply calls + * {@link #openAssetFile(Uri, String)}. + * + * <p>See {@link ClipData} for examples of the use and implementation + * of this method. + * <p> + * The returned AssetFileDescriptor can be a pipe or socket pair to enable + * streaming of data. + * + * <p class="note">For better interoperability with other applications, it is recommended + * that for any URIs that can be opened, you also support queries on them + * containing at least the columns specified by {@link android.provider.OpenableColumns}. + * You may also want to support other common columns if you have additional meta-data + * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED} + * in {@link android.provider.MediaStore.MediaColumns}.</p> + * + * @param uri The data in the content provider being queried. + * @param mimeTypeFilter The type of data the client desires. May be + * a pattern, such as *\/*, if the caller does not have specific type + * requirements; in this case the content provider will pick its best + * type matching the pattern. + * @param opts Additional options from the client. The definitions of + * these are specific to the content provider being called. + * @param signal A signal to cancel the operation in progress, or + * {@code null} if none. For example, if you are downloading a + * file from the network to service a "rw" mode request, you + * should periodically call + * {@link CancellationSignal#throwIfCanceled()} to check whether + * the client has canceled the request and abort the download. + * + * @return Returns a new AssetFileDescriptor from which the client can + * read data of the desired type. + * + * @throws FileNotFoundException Throws FileNotFoundException if there is + * no file associated with the given URI or the mode is invalid. + * @throws SecurityException Throws SecurityException if the caller does + * not have permission to access the data. + * @throws IllegalArgumentException Throws IllegalArgumentException if the + * content provider does not support the requested MIME type. + * + * @see #getStreamTypes(Uri, String) + * @see #openAssetFile(Uri, String) + * @see ClipDescription#compareMimeTypes(String, String) + */ + public AssetFileDescriptor openTypedAssetFile( + Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) + throws FileNotFoundException { + return openTypedAssetFile(uri, mimeTypeFilter, opts); + } + /** * Interface to write a stream of data to a pipe. Use with * {@link ContentProvider#openPipeHelper}. diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 8dffac7..024a521 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -63,14 +63,7 @@ public class ContentProviderClient { /** See {@link ContentProvider#query ContentProvider.query} */ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException { - try { - return query(url, projection, selection, selectionArgs, sortOrder, null); - } catch (DeadObjectException e) { - if (!mStable) { - mContentResolver.unstableProviderDied(mContentProvider); - } - throw e; - } + return query(url, projection, selection, selectionArgs, sortOrder, null); } /** See {@link ContentProvider#query ContentProvider.query} */ @@ -177,8 +170,25 @@ public class ContentProviderClient { */ public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException { + return openFile(url, mode, null); + } + + /** + * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that + * this <em>does not</em> + * take care of non-content: URIs such as file:. It is strongly recommended + * you use the {@link ContentResolver#openFileDescriptor + * ContentResolver.openFileDescriptor} API instead. + */ + public ParcelFileDescriptor openFile(Uri url, String mode, CancellationSignal signal) + throws RemoteException, FileNotFoundException { + ICancellationSignal remoteSignal = null; + if (signal != null) { + remoteSignal = mContentProvider.createCancellationSignal(); + signal.setRemote(remoteSignal); + } try { - return mContentProvider.openFile(mPackageName, url, mode); + return mContentProvider.openFile(mPackageName, url, mode, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -196,8 +206,25 @@ public class ContentProviderClient { */ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException { + return openAssetFile(url, mode, null); + } + + /** + * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}. + * Note that this <em>does not</em> + * take care of non-content: URIs such as file:. It is strongly recommended + * you use the {@link ContentResolver#openAssetFileDescriptor + * ContentResolver.openAssetFileDescriptor} API instead. + */ + public AssetFileDescriptor openAssetFile(Uri url, String mode, CancellationSignal signal) + throws RemoteException, FileNotFoundException { + ICancellationSignal remoteSignal = null; + if (signal != null) { + remoteSignal = mContentProvider.createCancellationSignal(); + signal.setRemote(remoteSignal); + } try { - return mContentProvider.openAssetFile(mPackageName, url, mode); + return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal); } catch (DeadObjectException e) { if (!mStable) { mContentResolver.unstableProviderDied(mContentProvider); @@ -208,10 +235,22 @@ public class ContentProviderClient { /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, - String mimeType, Bundle opts) + String mimeType, Bundle opts) throws RemoteException, FileNotFoundException { + return openTypedAssetFileDescriptor(uri, mimeType, opts, null); + } + + /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ + public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, + String mimeType, Bundle opts, CancellationSignal signal) throws RemoteException, FileNotFoundException { + ICancellationSignal remoteSignal = null; + if (signal != null) { + remoteSignal = mContentProvider.createCancellationSignal(); + signal.setRemote(remoteSignal); + } try { - return mContentProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts); + return mContentProvider.openTypedAssetFile( + mPackageName, uri, mimeType, opts, remoteSignal); } 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 6f822c1..744e68c 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -18,22 +18,20 @@ package android.content; import android.content.res.AssetFileDescriptor; import android.database.BulkCursorDescriptor; -import android.database.BulkCursorNative; import android.database.BulkCursorToCursorAdaptor; import android.database.Cursor; import android.database.CursorToBulkCursorAdaptor; import android.database.DatabaseUtils; -import android.database.IBulkCursor; import android.database.IContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Bundle; -import android.os.RemoteException; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.RemoteException; import java.io.FileNotFoundException; import java.util.ArrayList; @@ -227,9 +225,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String callingPkg = data.readString(); Uri url = Uri.CREATOR.createFromParcel(data); String mode = data.readString(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); ParcelFileDescriptor fd; - fd = openFile(callingPkg, url, mode); + fd = openFile(callingPkg, url, mode, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -247,9 +247,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String callingPkg = data.readString(); Uri url = Uri.CREATOR.createFromParcel(data); String mode = data.readString(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); AssetFileDescriptor fd; - fd = openAssetFile(callingPkg, url, mode); + fd = openAssetFile(callingPkg, url, mode, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -296,9 +298,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr Uri url = Uri.CREATOR.createFromParcel(data); String mimeType = data.readString(); Bundle opts = data.readBundle(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); AssetFileDescriptor fd; - fd = openTypedAssetFile(callingPkg, url, mimeType, opts); + fd = openTypedAssetFile(callingPkg, url, mimeType, opts, signal); reply.writeNoException(); if (fd != null) { reply.writeInt(1); @@ -538,7 +542,9 @@ final class ContentProviderProxy implements IContentProvider } } - public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode) + @Override + public ParcelFileDescriptor openFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -548,6 +554,7 @@ final class ContentProviderProxy implements IContentProvider data.writeString(callingPkg); url.writeToParcel(data, 0); data.writeString(mode); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0); @@ -561,7 +568,9 @@ final class ContentProviderProxy implements IContentProvider } } - public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode) + @Override + public AssetFileDescriptor openAssetFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -571,6 +580,7 @@ final class ContentProviderProxy implements IContentProvider data.writeString(callingPkg); url.writeToParcel(data, 0); data.writeString(mode); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0); @@ -629,8 +639,9 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType, - Bundle opts) throws RemoteException, FileNotFoundException { + Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -640,6 +651,7 @@ final class ContentProviderProxy implements IContentProvider url.writeToParcel(data, 0); data.writeString(mimeType); data.writeBundle(opts); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 5cabfee..a761a89 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -55,7 +55,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; - /** * This class provides applications access to the content model. * @@ -328,7 +327,7 @@ public abstract class ContentResolver { * content URL can be returned when opened as as stream with * {@link #openTypedAssetFileDescriptor}. Note that the types here are * not necessarily a superset of the type returned by {@link #getType} -- - * many content providers can not return a raw stream for the structured + * many content providers cannot return a raw stream for the structured * data that they contain. * * @param url A Uri identifying content (either a list or specific type), @@ -485,6 +484,9 @@ public abstract class ContentResolver { if (qCursor != null) { qCursor.close(); } + if (cancellationSignal != null) { + cancellationSignal.setRemote(null); + } if (unstableProvider != null) { releaseUnstableProvider(unstableProvider); } @@ -531,7 +533,7 @@ public abstract class ContentResolver { // with sufficient testing. return new FileInputStream(uri.getPath()); } else { - AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r"); + AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r", null); try { return fd != null ? fd.createInputStream() : null; } catch (IOException e) { @@ -571,7 +573,7 @@ public abstract class ContentResolver { */ public final OutputStream openOutputStream(Uri uri, String mode) throws FileNotFoundException { - AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode); + AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode, null); try { return fd != null ? fd.createOutputStream() : null; } catch (IOException e) { @@ -597,10 +599,64 @@ public abstract class ContentResolver { * * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information * on these schemes. + * <p> + * If opening with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor could be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" mode implies a file on disk that supports + * seeking. If possible, always use an exclusive mode to give the underlying + * {@link ContentProvider} the most flexibility. + * <p> + * If you are writing a file, and need to communicate an error to the + * provider, use {@link ParcelFileDescriptor#closeWithError(String)}. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openFile + * ContentProvider.openFile}. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException if no + * file exists under the URI or the mode is invalid. + * @see #openAssetFileDescriptor(Uri, String) + */ + public final ParcelFileDescriptor openFileDescriptor(Uri uri, String mode) + throws FileNotFoundException { + return openFileDescriptor(uri, mode, null); + } + + /** + * Open a raw file descriptor to access data under a URI. This + * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the + * underlying {@link ContentProvider#openFile} + * ContentProvider.openFile()} method, so will <em>not</em> work with + * providers that return sub-sections of files. If at all possible, + * you should use {@link #openAssetFileDescriptor(Uri, String)}. You + * will receive a FileNotFoundException exception if the provider returns a + * sub-section of a file. + * + * <h5>Accepts the following URI schemes:</h5> + * <ul> + * <li>content ({@link #SCHEME_CONTENT})</li> + * <li>file ({@link #SCHEME_FILE})</li> + * </ul> + * + * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information + * on these schemes. + * <p> + * If opening with the exclusive "r" or "w" modes, the returned + * ParcelFileDescriptor could be a pipe or socket pair to enable streaming + * of data. Opening with the "rw" mode implies a file on disk that supports + * seeking. If possible, always use an exclusive mode to give the underlying + * {@link ContentProvider} the most flexibility. + * <p> + * If you are writing a file, and need to communicate an error to the + * provider, use {@link ParcelFileDescriptor#closeWithError(String)}. * * @param uri The desired URI to open. * @param mode The file mode to use, as per {@link ContentProvider#openFile * ContentProvider.openFile}. + * @param cancellationSignal A signal to cancel the operation in progress, + * or null if none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. * @return Returns a new ParcelFileDescriptor pointing to the file. You * own this descriptor and are responsible for closing it when done. * @throws FileNotFoundException Throws FileNotFoundException if no @@ -608,8 +664,8 @@ public abstract class ContentResolver { * @see #openAssetFileDescriptor(Uri, String) */ public final ParcelFileDescriptor openFileDescriptor(Uri uri, - String mode) throws FileNotFoundException { - AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode); + String mode, CancellationSignal cancellationSignal) throws FileNotFoundException { + AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode, cancellationSignal); if (afd == null) { return null; } @@ -677,8 +733,64 @@ public abstract class ContentResolver { * @throws FileNotFoundException Throws FileNotFoundException of no * file exists under the URI or the mode is invalid. */ + public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, String mode) + throws FileNotFoundException { + return openAssetFileDescriptor(uri, mode, null); + } + + /** + * Open a raw file descriptor to access data under a URI. This + * interacts with the underlying {@link ContentProvider#openAssetFile} + * method of the provider associated with the given URI, to retrieve any file stored there. + * + * <h5>Accepts the following URI schemes:</h5> + * <ul> + * <li>content ({@link #SCHEME_CONTENT})</li> + * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li> + * <li>file ({@link #SCHEME_FILE})</li> + * </ul> + * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5> + * <p> + * A Uri object can be used to reference a resource in an APK file. The + * Uri should be one of the following formats: + * <ul> + * <li><code>android.resource://package_name/id_number</code><br/> + * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. + * For example <code>com.example.myapp</code><br/> + * <code>id_number</code> is the int form of the ID.<br/> + * The easiest way to construct this form is + * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre> + * </li> + * <li><code>android.resource://package_name/type/name</code><br/> + * <code>package_name</code> is your package name as listed in your AndroidManifest.xml. + * For example <code>com.example.myapp</code><br/> + * <code>type</code> is the string form of the resource type. For example, <code>raw</code> + * or <code>drawable</code>. + * <code>name</code> is the string form of the resource name. That is, whatever the file + * name was in your res directory, without the type extension. + * The easiest way to construct this form is + * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre> + * </li> + * </ul> + * + * <p>Note that if this function is called for read-only input (mode is "r") + * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor} + * for you with a MIME type of "*\/*". This allows such callers to benefit + * from any built-in data conversion that a provider implements. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile + * ContentProvider.openAssetFile}. + * @param cancellationSignal A signal to cancel the operation in progress, or null if + * none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * file exists under the URI or the mode is invalid. + */ public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, - String mode) throws FileNotFoundException { + String mode, CancellationSignal cancellationSignal) throws FileNotFoundException { String scheme = uri.getScheme(); if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { if (!"r".equals(mode)) { @@ -696,7 +808,7 @@ public abstract class ContentResolver { return new AssetFileDescriptor(pfd, 0, -1); } else { if ("r".equals(mode)) { - return openTypedAssetFileDescriptor(uri, "*/*", null); + return openTypedAssetFileDescriptor(uri, "*/*", null, cancellationSignal); } else { IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { @@ -706,8 +818,16 @@ public abstract class ContentResolver { AssetFileDescriptor fd = null; try { + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = unstableProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + try { - fd = unstableProvider.openAssetFile(mPackageName, uri, mode); + fd = unstableProvider.openAssetFile( + mPackageName, uri, mode, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -721,7 +841,8 @@ public abstract class ContentResolver { if (stableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); } - fd = stableProvider.openAssetFile(mPackageName, uri, mode); + fd = stableProvider.openAssetFile( + mPackageName, uri, mode, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -749,6 +870,9 @@ public abstract class ContentResolver { } catch (FileNotFoundException e) { throw e; } finally { + if (cancellationSignal != null) { + cancellationSignal.setRemote(null); + } if (stableProvider != null) { releaseProvider(stableProvider); } @@ -788,8 +912,45 @@ public abstract class ContentResolver { * @throws FileNotFoundException Throws FileNotFoundException of no * data of the desired type exists under the URI. */ + public final AssetFileDescriptor openTypedAssetFileDescriptor( + Uri uri, String mimeType, Bundle opts) throws FileNotFoundException { + return openTypedAssetFileDescriptor(uri, mimeType, opts, null); + } + + /** + * Open a raw file descriptor to access (potentially type transformed) + * data from a "content:" URI. This interacts with the underlying + * {@link ContentProvider#openTypedAssetFile} method of the provider + * associated with the given URI, to retrieve retrieve any appropriate + * data stream for the data stored there. + * + * <p>Unlike {@link #openAssetFileDescriptor}, this function only works + * with "content:" URIs, because content providers are the only facility + * with an associated MIME type to ensure that the returned data stream + * is of the desired type. + * + * <p>All text/* streams are encoded in UTF-8. + * + * @param uri The desired URI to open. + * @param mimeType The desired MIME type of the returned data. This can + * be a pattern such as *\/*, which will allow the content provider to + * select a type, though there is no way for you to determine what type + * it is returning. + * @param opts Additional provider-dependent options. + * @param cancellationSignal A signal to cancel the operation in progress, + * or null if none. If the operation is canceled, then + * {@link OperationCanceledException} will be thrown. + * @return Returns a new ParcelFileDescriptor from which you can read the + * data stream from the provider. Note that this may be a pipe, meaning + * you can't seek in it. The only seek you should do is if the + * AssetFileDescriptor contains an offset, to move to that offset before + * reading. You own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * data of the desired type exists under the URI. + */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, - String mimeType, Bundle opts) throws FileNotFoundException { + String mimeType, Bundle opts, CancellationSignal cancellationSignal) + throws FileNotFoundException { IContentProvider unstableProvider = acquireUnstableProvider(uri); if (unstableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); @@ -798,8 +959,16 @@ public abstract class ContentResolver { AssetFileDescriptor fd = null; try { + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = unstableProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + try { - fd = unstableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts); + fd = unstableProvider.openTypedAssetFile( + mPackageName, uri, mimeType, opts, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -813,7 +982,8 @@ public abstract class ContentResolver { if (stableProvider == null) { throw new FileNotFoundException("No content provider: " + uri); } - fd = stableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts); + fd = stableProvider.openTypedAssetFile( + mPackageName, uri, mimeType, opts, remoteCancellationSignal); if (fd == null) { // The provider will be released by the finally{} clause return null; @@ -841,6 +1011,9 @@ public abstract class ContentResolver { } catch (FileNotFoundException e) { throw e; } finally { + if (cancellationSignal != null) { + cancellationSignal.setRemote(null); + } if (stableProvider != null) { releaseProvider(stableProvider); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index ff81f72..cd1f87b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2375,7 +2375,7 @@ public abstract class Context { /** * {@link android.print.PrintManager} for printing and managing - * printers and print taks. + * printers and print tasks. * * @see #getSystemService * @see android.print.PrintManager diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 62b79f0..6ea8876 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -46,9 +46,11 @@ public interface IContentProvider extends IInterface { throws RemoteException; public int update(String callingPkg, Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException; - public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode) + public ParcelFileDescriptor openFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; - public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode) + public AssetFileDescriptor openAssetFile( + String callingPkg, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException; public ContentProviderResult[] applyBatch(String callingPkg, ArrayList<ContentProviderOperation> operations) @@ -60,7 +62,7 @@ public interface IContentProvider extends IInterface { // Data interchange. public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException; public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType, - Bundle opts) throws RemoteException, FileNotFoundException; + Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException; /* IPC constants */ static final String descriptor = "android.content.IContentProvider"; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 017ad98..c99f09c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -35,6 +35,7 @@ import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.StrictMode; +import android.provider.DocumentsContract; import android.util.AttributeSet; import android.util.Log; @@ -1192,7 +1193,7 @@ public class Intent implements Parcelable, Cloneable { /** * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST} - * containing an the names of the application package of foreground services at the time + * containing the application package names of foreground services at the time * of the assist request. This is an array of {@link String}s, with one entry * per service. */ @@ -2650,11 +2651,16 @@ public class Intent implements Parcelable, Cloneable { * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE} to * indicate this. * <p> - * All returned URIs can be opened as a stream with - * {@link ContentResolver#openInputStream(Uri)}. + * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that + * returned URIs can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. * <p> * Output: The URI of the item that was picked. This must be a content: URI - * so that any receiver can access it. + * so that any receiver can access it. If multiple documents were selected, + * they are returned in {@link #getClipData()}. + * + * @see DocumentsContract + * @see DocumentsContract#getOpenDocuments(Context) */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT"; @@ -2669,11 +2675,14 @@ public class Intent implements Parcelable, Cloneable { * optionally hint at the MIME type being created by setting * {@link #setType(String)}. * <p> - * All returned URIs can be opened as a stream with - * {@link ContentResolver#openOutputStream(Uri)}. + * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that + * returned URIs can be opened with + * {@link ContentResolver#openFileDescriptor(Uri, String)}. * <p> * Output: The URI of the item that was created. This must be a content: URI * so that any receiver can access it. + * + * @see DocumentsContract */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT"; diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index 7381e2c..fc2a885 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -25,9 +25,12 @@ import java.io.Closeable; /** * This interface provides random read-write access to the result set returned * by a database query. - * + * <p> * Cursor implementations are not required to be synchronized so code using a Cursor from multiple * threads should perform its own synchronization when using the Cursor. + * </p><p> + * Implementations should subclass {@link AbstractCursor}. + * </p> */ public interface Cursor extends Closeable { /* diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 7d65736..f12be5f 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -74,8 +74,7 @@ public final class DisplayManager { * richer second screen experiences. * </p> * - * @see android.app.Presentation for information about presenting content - * on secondary displays. + * @see android.app.Presentation * @see Display#FLAG_PRESENTATION * @see #getDisplays(String) */ @@ -138,8 +137,7 @@ public final class DisplayManager { * more special-purpose displays. * </p> * - * @see android.app.Presentation for information about presenting content - * on secondary displays. + * @see android.app.Presentation * @see #createVirtualDisplay * @see #DISPLAY_CATEGORY_PRESENTATION * @see Display#FLAG_PRESENTATION @@ -168,7 +166,7 @@ public final class DisplayManager { * The content of secure windows will be blanked if shown on this display. * </p> * - * @see Display#FLAG_SECURE for information about secure displays. + * @see Display#FLAG_SECURE * @see #createVirtualDisplay */ public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 1 << 2; @@ -325,6 +323,16 @@ public final class DisplayManager { mGlobal.connectWifiDisplay(deviceAddress); } + /** @hide */ + public void pauseWifiDisplay() { + mGlobal.pauseWifiDisplay(); + } + + /** @hide */ + public void resumeWifiDisplay() { + mGlobal.resumeWifiDisplay(); + } + /** * Disconnects from the current Wifi display. * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 10c14ff..936a086 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -287,6 +287,22 @@ public final class DisplayManagerGlobal { } } + public void pauseWifiDisplay() { + try { + mDm.pauseWifiDisplay(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to pause Wifi display.", ex); + } + } + + public void resumeWifiDisplay() { + try { + mDm.resumeWifiDisplay(); + } catch (RemoteException ex) { + Log.e(TAG, "Failed to resume Wifi display.", ex); + } + } + public void disconnectWifiDisplay() { try { mDm.disconnectWifiDisplay(); diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index afaf436..6b2c887 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -55,4 +55,10 @@ interface IDisplayManager { // No permissions required but must be same Uid as the creator. void releaseVirtualDisplay(in IBinder token); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void pauseWifiDisplay(); + + // Requires CONFIGURE_WIFI_DISPLAY permission. + void resumeWifiDisplay(); } diff --git a/core/java/android/hardware/display/WifiDisplaySessionInfo.java b/core/java/android/hardware/display/WifiDisplaySessionInfo.java new file mode 100644 index 0000000..33d2725 --- /dev/null +++ b/core/java/android/hardware/display/WifiDisplaySessionInfo.java @@ -0,0 +1,116 @@ +/* + * 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.hardware.display; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains information regarding a wifi display session + * (such as session id, source ip address, etc.). This is needed for + * Wifi Display Certification process. + * <p> + * This object is immutable. + * </p> + * + * @hide + */ +public final class WifiDisplaySessionInfo implements Parcelable { + private final boolean mClient; + private final int mSessionId; + private final String mGroupId; + private final String mPassphrase; + private final String mIP; + + public static final Creator<WifiDisplaySessionInfo> CREATOR = + new Creator<WifiDisplaySessionInfo>() { + @Override + public WifiDisplaySessionInfo createFromParcel(Parcel in) { + boolean client = (in.readInt() != 0); + int session = in.readInt(); + String group = in.readString(); + String pp = in.readString(); + String ip = in.readString(); + + return new WifiDisplaySessionInfo(client, session, group, pp, ip); + } + + @Override + public WifiDisplaySessionInfo[] newArray(int size) { + return new WifiDisplaySessionInfo[size]; + } + }; + + public WifiDisplaySessionInfo() { + this(true, 0, "", "", ""); + } + + public WifiDisplaySessionInfo( + boolean client, int session, String group, String pp, String ip) { + mClient = client; + mSessionId = session; + mGroupId = group; + mPassphrase = pp; + mIP = ip; + } + + public boolean isClient() { + return mClient; + } + + public int getSessionId() { + return mSessionId; + } + + public String getGroupId() { + return mGroupId; + } + + public String getPassphrase() { + return mPassphrase; + } + + public String getIP() { + return mIP; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mClient ? 1 : 0); + dest.writeInt(mSessionId); + dest.writeString(mGroupId); + dest.writeString(mPassphrase); + dest.writeString(mIP); + } + + @Override + public int describeContents() { + return 0; + } + + // For debugging purposes only. + @Override + public String toString() { + return "WifiDisplaySessionInfo:" + +"\n Client/Owner: " + (mClient ? "Client":"Owner") + +"\n GroupId: " + mGroupId + +"\n Passphrase: " + mPassphrase + +"\n SessionId: " + mSessionId + +"\n IP Address: " + mIP + ; + } +} diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java index 77acdc0..5216727 100644 --- a/core/java/android/hardware/display/WifiDisplayStatus.java +++ b/core/java/android/hardware/display/WifiDisplayStatus.java @@ -39,6 +39,9 @@ public final class WifiDisplayStatus implements Parcelable { private final WifiDisplay mActiveDisplay; private final WifiDisplay[] mDisplays; + /** Session info needed for Miracast Certification */ + private final WifiDisplaySessionInfo mSessionInfo; + /** Feature state: Wifi display is not available on this device. */ public static final int FEATURE_STATE_UNAVAILABLE = 0; /** Feature state: Wifi display is disabled, probably because Wifi is disabled. */ @@ -76,8 +79,11 @@ public final class WifiDisplayStatus implements Parcelable { displays[i] = WifiDisplay.CREATOR.createFromParcel(in); } + WifiDisplaySessionInfo sessionInfo = + WifiDisplaySessionInfo.CREATOR.createFromParcel(in); + return new WifiDisplayStatus(featureState, scanState, activeDisplayState, - activeDisplay, displays); + activeDisplay, displays, sessionInfo); } public WifiDisplayStatus[] newArray(int size) { @@ -87,11 +93,11 @@ public final class WifiDisplayStatus implements Parcelable { public WifiDisplayStatus() { this(FEATURE_STATE_UNAVAILABLE, SCAN_STATE_NOT_SCANNING, DISPLAY_STATE_NOT_CONNECTED, - null, WifiDisplay.EMPTY_ARRAY); + null, WifiDisplay.EMPTY_ARRAY, null); } - public WifiDisplayStatus(int featureState, int scanState, - int activeDisplayState, WifiDisplay activeDisplay, WifiDisplay[] displays) { + public WifiDisplayStatus(int featureState, int scanState, int activeDisplayState, + WifiDisplay activeDisplay, WifiDisplay[] displays, WifiDisplaySessionInfo sessionInfo) { if (displays == null) { throw new IllegalArgumentException("displays must not be null"); } @@ -101,6 +107,8 @@ public final class WifiDisplayStatus implements Parcelable { mActiveDisplayState = activeDisplayState; mActiveDisplay = activeDisplay; mDisplays = displays; + + mSessionInfo = (sessionInfo != null) ? sessionInfo : new WifiDisplaySessionInfo(); } /** @@ -144,13 +152,20 @@ public final class WifiDisplayStatus implements Parcelable { /** * Gets the list of Wifi displays, returns a combined list of all available - * Wifi displays as reported by the most recent scan, and all remembered + * Wifi displays as reported by the most recent scan, and all remembered * Wifi displays (not necessarily available at the time). */ public WifiDisplay[] getDisplays() { return mDisplays; } + /** + * Gets the Wifi display session info (required for certification only) + */ + public WifiDisplaySessionInfo getSessionInfo() { + return mSessionInfo; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mFeatureState); @@ -168,6 +183,8 @@ public final class WifiDisplayStatus implements Parcelable { for (WifiDisplay display : mDisplays) { display.writeToParcel(dest, flags); } + + mSessionInfo.writeToParcel(dest, flags); } @Override @@ -183,6 +200,7 @@ public final class WifiDisplayStatus implements Parcelable { + ", activeDisplayState=" + mActiveDisplayState + ", activeDisplay=" + mActiveDisplay + ", displays=" + Arrays.toString(mDisplays) + + ", sessionInfo=" + mSessionInfo + "}"; } } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index da7647a..65c9220 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -16,6 +16,9 @@ package android.provider; +import static android.net.TrafficStats.KB_IN_BYTES; +import static libcore.io.OsConstants.SEEK_SET; + import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; @@ -30,19 +33,49 @@ import android.graphics.BitmapFactory; import android.graphics.Point; import android.net.Uri; import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.OnCloseListener; import android.util.Log; import com.google.android.collect.Lists; +import libcore.io.ErrnoException; +import libcore.io.IoBridge; import libcore.io.IoUtils; +import libcore.io.Libcore; import java.io.FileDescriptor; import java.io.IOException; import java.util.List; /** - * The contract between a storage backend and the platform. Contains definitions - * for the supported URIs and columns. + * Defines the contract between a documents provider and the platform. + * <p> + * A document provider is a {@link ContentProvider} that presents a set of + * documents in a hierarchical structure. The system provides UI that visualizes + * all available document providers, offering users the ability to open existing + * documents or create new documents. + * <p> + * Each provider expresses one or more "roots" which each serve as the top-level + * of a tree. For example, a root could represent an account, or a physical + * storage device. Under each root, documents are referenced by a unique + * {@link DocumentColumns#DOC_ID}, and each root starts at the + * {@link Documents#DOC_ID_ROOT} document. + * <p> + * Documents can be either an openable file (with a specific MIME type), or a + * directory containing additional documents (with the + * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different + * capabilities, as described by {@link DocumentColumns#FLAGS}. The same + * {@link DocumentColumns#DOC_ID} can be included in multiple directories. + * <p> + * Document providers must be protected with the + * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can + * only be requested by the system. The system-provided UI then issues narrow + * Uri permission grants for individual documents when the user explicitly picks + * documents. + * + * @see Intent#ACTION_OPEN_DOCUMENT + * @see Intent#ACTION_CREATE_DOCUMENT */ public final class DocumentsContract { private static final String TAG = "Documents"; @@ -59,6 +92,9 @@ public final class DocumentsContract { /** {@hide} */ public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED"; + /** + * Constants for individual documents. + */ public static class Documents { private Documents() { } @@ -73,7 +109,7 @@ public final class DocumentsContract { /** * {@link DocumentColumns#DOC_ID} value representing the root directory of a - * storage root. + * documents root. */ public static final String DOC_ID_ROOT = "0"; @@ -144,8 +180,8 @@ public final class DocumentsContract { /** * Extra boolean flag included in a directory {@link Cursor#getExtras()} - * indicating that the backend can provide additional data if requested, - * such as additional search results. + * indicating that the document provider can provide additional data if + * requested, such as additional search results. */ public static final String EXTRA_HAS_MORE = "has_more"; @@ -170,21 +206,24 @@ public final class DocumentsContract { private static final String PARAM_LOCAL_ONLY = "localOnly"; /** - * Build URI representing the roots in a storage backend. + * Build Uri representing the roots offered by a document provider. */ public static Uri buildRootsUri(String authority) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).appendPath(PATH_ROOTS).build(); } + /** + * Build Uri representing a specific root offered by a document provider. + */ public static Uri buildRootUri(String authority, String rootId) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build(); } /** - * Build URI representing the given {@link DocumentColumns#DOC_ID} in a - * storage root. + * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a + * document provider. */ public static Uri buildDocumentUri(String authority, String rootId, String docId) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) @@ -193,8 +232,8 @@ public final class DocumentsContract { } /** - * Build URI representing the contents of the given directory in a storage - * backend. The given document must be {@link Documents#MIME_TYPE_DIR}. + * Build Uri representing the contents of the given directory in a document + * provider. The given document must be {@link Documents#MIME_TYPE_DIR}. */ public static Uri buildContentsUri(String authority, String rootId, String docId) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) @@ -203,8 +242,9 @@ public final class DocumentsContract { } /** - * Build URI representing a search for matching documents under a directory - * in a storage backend. + * Build Uri representing a search for matching documents under a specific + * directory in a document provider. The given document must have + * {@link Documents#FLAG_SUPPORTS_SEARCH}. */ public static Uri buildSearchUri(String authority, String rootId, String docId, String query) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) @@ -212,20 +252,36 @@ public final class DocumentsContract { .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build(); } + /** + * Convenience method for {@link #buildDocumentUri(String, String, String)}, + * extracting authority and root from the given Uri. + */ public static Uri buildDocumentUri(Uri relatedUri, String docId) { return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId); } + /** + * Convenience method for {@link #buildContentsUri(String, String, String)}, + * extracting authority and root from the given Uri. + */ public static Uri buildContentsUri(Uri relatedUri) { return buildContentsUri( relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri)); } + /** + * Convenience method for + * {@link #buildSearchUri(String, String, String, String)}, extracting + * authority and root from the given Uri. + */ public static Uri buildSearchUri(Uri relatedUri, String query) { return buildSearchUri( relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query); } + /** + * Extract the {@link RootColumns#ROOT_ID} from the given Uri. + */ public static String getRootId(Uri documentUri) { final List<String> paths = documentUri.getPathSegments(); if (paths.size() < 2) { @@ -237,6 +293,9 @@ public final class DocumentsContract { return paths.get(1); } + /** + * Extract the {@link DocumentColumns#DOC_ID} from the given Uri. + */ public static String getDocId(Uri documentUri) { final List<String> paths = documentUri.getPathSegments(); if (paths.size() < 4) { @@ -252,15 +311,17 @@ public final class DocumentsContract { } /** - * Return requested search query from the given Uri. + * Return requested search query from the given Uri, as constructed by + * {@link #buildSearchUri(String, String, String, String)}. */ public static String getSearchQuery(Uri documentUri) { return documentUri.getQueryParameter(PARAM_QUERY); } /** - * Mark the given Uri to indicate that only locally-available contents - * should be returned. + * Mark the given Uri to indicate that only locally-available data should be + * returned. That is, no network connections should be initiated to provide + * the metadata or content. */ public static Uri setLocalOnly(Uri documentUri) { return documentUri.buildUpon() @@ -268,20 +329,21 @@ public final class DocumentsContract { } /** - * Return if the given Uri is requesting that only locally-available content - * be returned. That is, no network connections should be initiated to - * provide the metadata or content. + * Return if the given Uri is requesting that only locally-available data be + * returned. That is, no network connections should be initiated to provide + * the metadata or content. */ public static boolean isLocalOnly(Uri documentUri) { return documentUri.getBooleanQueryParameter(PARAM_LOCAL_ONLY, false); } /** - * These are standard columns for document URIs. Storage backend providers - * <em>must</em> support at least these columns when queried. + * Standard columns for document queries. Document providers <em>must</em> + * support at least these columns when queried. * - * @see Intent#ACTION_OPEN_DOCUMENT - * @see Intent#ACTION_CREATE_DOCUMENT + * @see DocumentsContract#buildDocumentUri(String, String, String) + * @see DocumentsContract#buildContentsUri(String, String, String) + * @see DocumentsContract#buildSearchUri(String, String, String, String) */ public interface DocumentColumns extends OpenableColumns { /** @@ -296,8 +358,8 @@ public final class DocumentsContract { /** * MIME type of a document, matching the value returned by * {@link ContentResolver#getType(android.net.Uri)}. This field must be - * provided when a new document is created, but after that the field is - * read-only. + * provided when a new document is created. This field is read-only to + * document clients. * <p> * Type: STRING * @@ -308,7 +370,9 @@ public final class DocumentsContract { /** * Timestamp when a document was last modified, in milliseconds since * January 1, 1970 00:00:00.0 UTC. This field is read-only to document - * clients. + * clients. Document providers can update this field using events from + * {@link OnCloseListener} or other reliable + * {@link ParcelFileDescriptor} transport. * <p> * Type: INTEGER (long) * @@ -325,13 +389,17 @@ public final class DocumentsContract { public static final String FLAGS = "flags"; /** - * Summary for this document, or {@code null} to omit. + * Summary for this document, or {@code null} to omit. This field is + * read-only to document clients. * <p> * Type: STRING */ public static final String SUMMARY = "summary"; } + /** + * Constants for individual document roots. + */ public static class Roots { private Roots() { } @@ -340,7 +408,8 @@ public final class DocumentsContract { public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root"; /** - * Root that represents a cloud-based storage service. + * Root that represents a storage service, such as a cloud-based + * service. * * @see RootColumns#ROOT_TYPE */ @@ -371,15 +440,17 @@ public final class DocumentsContract { } /** - * These are standard columns for the roots URI. + * Standard columns for document root queries. * * @see DocumentsContract#buildRootsUri(String) + * @see DocumentsContract#buildRootUri(String, String) */ public interface RootColumns { public static final String ROOT_ID = "root_id"; /** - * Storage root type, use for clustering. + * Storage root type, use for clustering. This field is read-only to + * document clients. * <p> * Type: INTEGER (int) * @@ -389,8 +460,9 @@ public final class DocumentsContract { public static final String ROOT_TYPE = "root_type"; /** - * Icon resource ID for this storage root, or {@code 0} to use the - * default {@link ProviderInfo#icon}. + * Icon resource ID for this storage root, or {@code null} to use the + * default {@link ProviderInfo#icon}. This field is read-only to + * document clients. * <p> * Type: INTEGER (int) */ @@ -398,22 +470,25 @@ public final class DocumentsContract { /** * Title for this storage root, or {@code null} to use the default - * {@link ProviderInfo#labelRes}. + * {@link ProviderInfo#labelRes}. This field is read-only to document + * clients. * <p> * Type: STRING */ public static final String TITLE = "title"; /** - * Summary for this storage root, or {@code null} to omit. + * Summary for this storage root, or {@code null} to omit. This field is + * read-only to document clients. * <p> * Type: STRING */ public static final String SUMMARY = "summary"; /** - * Number of free bytes of available in this storage root, or -1 if - * unknown or unbounded. + * Number of free bytes of available in this storage root, or + * {@code null} if unknown or unbounded. This field is read-only to + * document clients. * <p> * Type: INTEGER (long) */ @@ -452,31 +527,59 @@ public final class DocumentsContract { /** * Return thumbnail representing the document at the given URI. Callers are - * responsible for their own caching. Given document must have + * responsible for their own in-memory caching. Given document must have * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set. * * @return decoded thumbnail, or {@code null} if problem was encountered. */ public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) { - final Bundle opts = new Bundle(); - opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size); + final Bundle openOpts = new Bundle(); + openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); AssetFileDescriptor afd = null; try { - afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts); + afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts); final FileDescriptor fd = afd.getFileDescriptor(); - final BitmapFactory.Options bitmapOpts = new BitmapFactory.Options(); + final long offset = afd.getStartOffset(); + final long length = afd.getDeclaredLength(); + + // Some thumbnails might be a region inside a larger file, such as + // an EXIF thumbnail. Since BitmapFactory aggressively seeks around + // the entire file, we read the region manually. + byte[] region = null; + if (offset > 0 && length <= 64 * KB_IN_BYTES) { + region = new byte[(int) length]; + Libcore.os.lseek(fd, offset, SEEK_SET); + if (IoBridge.read(fd, region, 0, region.length) != region.length) { + region = null; + } + } - bitmapOpts.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); + // We requested a rough thumbnail size, but the remote size may have + // returned something giant, so defensively scale down as needed. + final BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + if (region != null) { + BitmapFactory.decodeByteArray(region, 0, region.length, opts); + } else { + BitmapFactory.decodeFileDescriptor(fd, null, opts); + } - final int widthSample = bitmapOpts.outWidth / size.x; - final int heightSample = bitmapOpts.outHeight / size.y; + final int widthSample = opts.outWidth / size.x; + final int heightSample = opts.outHeight / size.y; - bitmapOpts.inJustDecodeBounds = false; - bitmapOpts.inSampleSize = Math.min(widthSample, heightSample); - return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); + opts.inJustDecodeBounds = false; + opts.inSampleSize = Math.min(widthSample, heightSample); + Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); + if (region != null) { + return BitmapFactory.decodeByteArray(region, 0, region.length, opts); + } else { + return BitmapFactory.decodeFileDescriptor(fd, null, opts); + } + } catch (ErrnoException e) { + Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); + return null; } catch (IOException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b3309e1..0b51b8a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5075,6 +5075,14 @@ public final class Settings { public static final String WIFI_DISPLAY_ON = "wifi_display_on"; /** + * Whether Wifi display certification mode is enabled/disabled + * 0=disabled. 1=enabled. + * @hide + */ + public static final String WIFI_DISPLAY_CERTIFICATION_ON = + "wifi_display_certification_on"; + + /** * Whether to notify the user of open networks. * <p> * If not connected and the scan results have an open network, we will diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 521ba81..1f2ab93 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -138,6 +138,7 @@ public class LockPatternUtils { = "lockscreen.biometricweakeverchosen"; public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS = "lockscreen.power_button_instantly_locks"; + public final static String LOCKSCREEN_WIDGETS_ENABLED = "lockscreen.widgets_enabled"; public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory"; @@ -1053,28 +1054,38 @@ public class LockPatternUtils { return nextAlarm; } - private boolean getBoolean(String secureSettingKey, boolean defaultValue) { + private boolean getBoolean(String secureSettingKey, boolean defaultValue, int userId) { try { - return getLockSettings().getBoolean(secureSettingKey, defaultValue, - getCurrentOrCallingUserId()); + return getLockSettings().getBoolean(secureSettingKey, defaultValue, userId); } catch (RemoteException re) { return defaultValue; } } - private void setBoolean(String secureSettingKey, boolean enabled) { + private boolean getBoolean(String secureSettingKey, boolean defaultValue) { + return getBoolean(secureSettingKey, defaultValue, getCurrentOrCallingUserId()); + } + + private void setBoolean(String secureSettingKey, boolean enabled, int userId) { try { - getLockSettings().setBoolean(secureSettingKey, enabled, getCurrentOrCallingUserId()); + getLockSettings().setBoolean(secureSettingKey, enabled, userId); } catch (RemoteException re) { // What can we do? Log.e(TAG, "Couldn't write boolean " + secureSettingKey + re); } } + private void setBoolean(String secureSettingKey, boolean enabled) { + setBoolean(secureSettingKey, enabled, getCurrentOrCallingUserId()); + } + public int[] getAppWidgets() { + return getAppWidgets(UserHandle.USER_CURRENT); + } + + private int[] getAppWidgets(int userId) { String appWidgetIdString = Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS, - UserHandle.USER_CURRENT); + mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS, userId); String delims = ","; if (appWidgetIdString != null && appWidgetIdString.length() > 0) { String[] appWidgetStringIds = appWidgetIdString.split(delims); @@ -1361,4 +1372,35 @@ public class LockPatternUtils { return false; } + /** + * Determine whether the user has selected any non-system widgets in keyguard + * + * @return true if widgets have been selected + */ + public boolean hasWidgetsEnabledInKeyguard(int userid) { + int widgets[] = getAppWidgets(userid); + for (int i = 0; i < widgets.length; i++) { + if (widgets[i] > 0) { + return true; + } + } + return false; + } + + public boolean getWidgetsEnabled() { + return getWidgetsEnabled(getCurrentOrCallingUserId()); + } + + public boolean getWidgetsEnabled(int userId) { + return getBoolean(LOCKSCREEN_WIDGETS_ENABLED, false, userId); + } + + public void setWidgetsEnabled(boolean enabled) { + setWidgetsEnabled(enabled, getCurrentOrCallingUserId()); + } + + public void setWidgetsEnabled(boolean enabled, int userId) { + setBoolean(LOCKSCREEN_WIDGETS_ENABLED, enabled, userId); + } + } diff --git a/core/jni/android_media_RemoteDisplay.cpp b/core/jni/android_media_RemoteDisplay.cpp index 80d13be..3fd8ed9 100644 --- a/core/jni/android_media_RemoteDisplay.cpp +++ b/core/jni/android_media_RemoteDisplay.cpp @@ -61,7 +61,7 @@ protected: public: virtual void onDisplayConnected(const sp<IGraphicBufferProducer>& bufferProducer, - uint32_t width, uint32_t height, uint32_t flags) { + uint32_t width, uint32_t height, uint32_t flags, uint32_t session) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jobject surfaceObj = android_view_Surface_createFromIGraphicBufferProducer(env, bufferProducer); @@ -73,7 +73,7 @@ public: env->CallVoidMethod(mRemoteDisplayObjGlobal, gRemoteDisplayClassInfo.notifyDisplayConnected, - surfaceObj, width, height, flags); + surfaceObj, width, height, flags, session); env->DeleteLocalRef(surfaceObj); checkAndClearExceptionFromCallback(env, "notifyDisplayConnected"); } @@ -117,6 +117,14 @@ public: mDisplay->dispose(); } + void pause() { + mDisplay->pause(); + } + + void resume() { + mDisplay->resume(); + } + private: sp<IRemoteDisplay> mDisplay; sp<NativeRemoteDisplayClient> mClient; @@ -149,6 +157,16 @@ static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr return reinterpret_cast<jint>(wrapper); } +static void nativePause(JNIEnv* env, jobject remoteDisplayObj, jint ptr) { + NativeRemoteDisplay* wrapper = reinterpret_cast<NativeRemoteDisplay*>(ptr); + wrapper->pause(); +} + +static void nativeResume(JNIEnv* env, jobject remoteDisplayObj, jint ptr) { + NativeRemoteDisplay* wrapper = reinterpret_cast<NativeRemoteDisplay*>(ptr); + wrapper->resume(); +} + static void nativeDispose(JNIEnv* env, jobject remoteDisplayObj, jint ptr) { NativeRemoteDisplay* wrapper = reinterpret_cast<NativeRemoteDisplay*>(ptr); delete wrapper; @@ -161,6 +179,10 @@ static JNINativeMethod gMethods[] = { (void*)nativeListen }, {"nativeDispose", "(I)V", (void*)nativeDispose }, + {"nativePause", "(I)V", + (void*)nativePause }, + {"nativeResume", "(I)V", + (void*)nativeResume }, }; int register_android_media_RemoteDisplay(JNIEnv* env) @@ -171,7 +193,7 @@ int register_android_media_RemoteDisplay(JNIEnv* env) jclass clazz = env->FindClass("android/media/RemoteDisplay"); gRemoteDisplayClassInfo.notifyDisplayConnected = env->GetMethodID(clazz, "notifyDisplayConnected", - "(Landroid/view/Surface;III)V"); + "(Landroid/view/Surface;IIII)V"); gRemoteDisplayClassInfo.notifyDisplayDisconnected = env->GetMethodID(clazz, "notifyDisplayDisconnected", "()V"); gRemoteDisplayClassInfo.notifyDisplayError = diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0d1ace8..faf6e63 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1900,14 +1900,14 @@ android:description="@string/permdesc_bindAccessibilityService" android:protectionLevel="signature" /> - <!-- Must be required by an {@link android.printservice.PrintService}, + <!-- Must be required by a {@link android.printservice.PrintService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_PRINT_SERVICE" android:label="@string/permlab_bindPrintService" android:description="@string/permdesc_bindPrintService" android:protectionLevel="signature" /> - <!-- Must be required by an {@link android.nfc.cardemulation.HostApduService} + <!-- Must be required by a {@link android.nfc.cardemulation.HostApduService} or {@link android.nfc.cardemulation.OffHostApduService} to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_NFC_SERVICE" @@ -1931,7 +1931,7 @@ android:description="@string/permdesc_bindTextService" android:protectionLevel="signature" /> - <!-- Must be required by an {@link android.net.VpnService}, + <!-- Must be required by a {@link android.net.VpnService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_VPN_SERVICE" android:label="@string/permlab_bindVpnService" @@ -1953,7 +1953,7 @@ android:protectionLevel="signature" /> <!-- Required to add or remove another application as a device admin. - <p/>Not for use by third-party apps. --> + <p/>Not for use by third-party applications. --> <permission android:name="android.permission.MANAGE_DEVICE_ADMINS" android:label="@string/permlab_manageDeviceAdmins" android:description="@string/permdesc_manageDeviceAdmins" diff --git a/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png b/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png Binary files differindex 0820f07..a36fa36 100644 --- a/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png +++ b/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index 10cdab0..e2606d6 100644 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -63,6 +63,8 @@ public class DrmManagerClient { private final CloseGuard mCloseGuard = CloseGuard.get(); + private static final String EXTENDED_INFO_DATA = "extended_info_data"; + static { // Load the respective library System.loadLibrary("drmframework_jni"); @@ -184,8 +186,22 @@ public class DrmManagerClient { DrmManagerClient instance = (DrmManagerClient)((WeakReference)thisReference).get(); if (null != instance && null != instance.mInfoHandler) { + DrmInfoEvent event = new DrmInfoEvent(uniqueId, infoType, message); + Message m = instance.mInfoHandler.obtainMessage( + InfoHandler.INFO_EVENT_TYPE, event); + instance.mInfoHandler.sendMessage(m); + } + } + + private static void notify( + Object thisReference, int uniqueId, int infoType, String message, + HashMap<String, Object> attributes) { + DrmManagerClient instance = (DrmManagerClient)((WeakReference)thisReference).get(); + + if (null != instance && null != instance.mInfoHandler) { + DrmInfoEvent event = new DrmInfoEvent(uniqueId, infoType, message, attributes); Message m = instance.mInfoHandler.obtainMessage( - InfoHandler.INFO_EVENT_TYPE, uniqueId, infoType, message); + InfoHandler.INFO_EVENT_TYPE, event); instance.mInfoHandler.sendMessage(m); } } @@ -198,23 +214,25 @@ public class DrmManagerClient { } public void handleMessage(Message msg) { - DrmInfoEvent info = null; + DrmInfoEvent info = (DrmInfoEvent) msg.obj; DrmErrorEvent error = null; + int uniqueId; + int eventType; + String message; switch (msg.what) { case InfoHandler.INFO_EVENT_TYPE: - int uniqueId = msg.arg1; - int infoType = msg.arg2; - String message = msg.obj.toString(); + uniqueId = info.getUniqueId(); + eventType = info.getType(); + message = info.getMessage(); - switch (infoType) { + switch (eventType) { case DrmInfoEvent.TYPE_REMOVE_RIGHTS: { try { DrmUtils.removeFile(message); } catch (IOException e) { e.printStackTrace(); } - info = new DrmInfoEvent(uniqueId, infoType, message); break; } case DrmInfoEvent.TYPE_ALREADY_REGISTERED_BY_ANOTHER_ACCOUNT: @@ -222,11 +240,11 @@ public class DrmManagerClient { case DrmInfoEvent.TYPE_WAIT_FOR_RIGHTS: case DrmInfoEvent.TYPE_ACCOUNT_ALREADY_REGISTERED: case DrmInfoEvent.TYPE_RIGHTS_REMOVED: { - info = new DrmInfoEvent(uniqueId, infoType, message); break; } default: - error = new DrmErrorEvent(uniqueId, infoType, message); + info = null; + error = new DrmErrorEvent(uniqueId, eventType, message); break; } diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp index baddf62..7fce3d0 100644 --- a/drm/jni/android_drm_DrmManagerClient.cpp +++ b/drm/jni/android_drm_DrmManagerClient.cpp @@ -169,11 +169,49 @@ void JNIOnInfoListener::onInfo(const DrmInfoEvent& event) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jstring message = env->NewStringUTF(event.getMessage().string()); ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().string()); - - env->CallStaticVoidMethod( - mClass, - env->GetStaticMethodID(mClass, "notify", "(Ljava/lang/Object;IILjava/lang/String;)V"), - mObject, uniqueId, type, message); + const DrmBuffer& drmBuffer = event.getData(); + if (event.getCount() > 0 || drmBuffer.length > 0) { + jclass hashMapClazz = env->FindClass("java/util/HashMap"); + jmethodID hashMapInitId = env->GetMethodID(hashMapClazz, "<init>", "()V"); + jmethodID hashMapPutId = env->GetMethodID(hashMapClazz, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + jobject hashMapObject = env->NewObject(hashMapClazz, hashMapInitId); + env->DeleteLocalRef(hashMapClazz); + + if (0 < drmBuffer.length) { + jfieldID fid = env->GetStaticFieldID( + mClass, "EXTENDED_INFO_DATA", "Ljava/lang/String;"); + jstring key = (jstring) env->GetStaticObjectField(mClass, fid); + + jbyteArray valueByte = env->NewByteArray(drmBuffer.length); + env->SetByteArrayRegion(valueByte, 0, drmBuffer.length, (jbyte*) drmBuffer.data); + env->CallObjectMethod(hashMapObject, hashMapPutId, key, valueByte); + env->DeleteLocalRef(valueByte); + env->DeleteLocalRef(key); + } + DrmInfoEvent::KeyIterator keyIt = event.keyIterator(); + while (keyIt.hasNext()) { + String8 mapKey = keyIt.next(); + jstring key = env->NewStringUTF(mapKey.string()); + jstring value = env->NewStringUTF(event.get(mapKey).string()); + env->CallObjectMethod(hashMapObject, hashMapPutId, key, value); + env->DeleteLocalRef(value); + env->DeleteLocalRef(key); + } + env->CallStaticVoidMethod( + mClass, + env->GetStaticMethodID(mClass, "notify", + "(Ljava/lang/Object;IILjava/lang/String;Ljava/util/HashMap;)V"), + mObject, uniqueId, type, message, hashMapObject); + env->DeleteLocalRef(hashMapObject); + } else { + env->CallStaticVoidMethod( + mClass, + env->GetStaticMethodID(mClass, "notify", + "(Ljava/lang/Object;IILjava/lang/String;)V"), + mObject, uniqueId, type, message); + } + env->DeleteLocalRef(message); } static Mutex sLock; diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index 362b586..7d05a74 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -61,6 +61,7 @@ public class Allocation extends BaseObj { Bitmap mBitmap; int mUsage; Allocation mAdaptedAllocation; + int mSize; boolean mConstrainedLOD; boolean mConstrainedFace; @@ -268,10 +269,22 @@ public class Allocation extends BaseObj { mType = t; mUsage = usage; + mSize = mType.getCount() * mType.getElement().getBytesSize(); if (t != null) { updateCacheInfo(t); } + try { + RenderScript.registerNativeAllocation.invoke(RenderScript.sRuntime, mSize); + } catch (Exception e) { + Log.e(RenderScript.LOG_TAG, "Couldn't invoke registerNativeAllocation:" + e); + throw new RSRuntimeException("Couldn't invoke registerNativeAllocation:" + e); + } + } + + protected void finalize() throws Throwable { + RenderScript.registerNativeFree.invoke(RenderScript.sRuntime, mSize); + super.finalize(); } private void validateIsInt32() { diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 4de4766..854f079 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -18,6 +18,7 @@ package android.renderscript; import java.io.File; import java.lang.reflect.Field; +import java.lang.reflect.Method; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -61,11 +62,24 @@ public class RenderScript { static boolean sInitialized; native static void _nInit(); + static Object sRuntime; + static Method registerNativeAllocation; + static Method registerNativeFree; static { sInitialized = false; if (!SystemProperties.getBoolean("config.disable_renderscript", false)) { try { + Class<?> vm_runtime = Class.forName("dalvik.system.VMRuntime"); + Method get_runtime = vm_runtime.getDeclaredMethod("getRuntime"); + sRuntime = get_runtime.invoke(null); + registerNativeAllocation = vm_runtime.getDeclaredMethod("registerNativeAllocation", Integer.TYPE); + registerNativeFree = vm_runtime.getDeclaredMethod("registerNativeFree", Integer.TYPE); + } catch (Exception e) { + Log.e(LOG_TAG, "Error loading GC methods: " + e); + throw new RSRuntimeException("Error loading GC methods: " + e); + } + try { System.loadLibrary("rs_jni"); _nInit(); sInitialized = true; diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 4cd3e37..20eb356 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -291,6 +291,20 @@ public class ExifInterface { } /** + * Returns the offset and length of thumbnail inside the JPEG file, or + * {@code null} if there is no thumbnail. + * + * @return two-element array, the offset in the first value, and length in + * the second, or {@code null} if no thumbnail was found. + * @hide + */ + public long[] getThumbnailRange() { + synchronized (sLock) { + return getThumbnailRangeNative(mFilename); + } + } + + /** * Stores the latitude and longitude value in a float array. The first element is * the latitude, and the second element is the longitude. Returns false if the * Exif tags are not available. @@ -416,4 +430,6 @@ public class ExifInterface { private native void commitChangesNative(String fileName); private native byte[] getThumbnailNative(String fileName); + + private native long[] getThumbnailRangeNative(String fileName); } diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java index b463d26..7afce1a 100644 --- a/media/java/android/media/RemoteDisplay.java +++ b/media/java/android/media/RemoteDisplay.java @@ -42,6 +42,8 @@ public final class RemoteDisplay { private native int nativeListen(String iface); private native void nativeDispose(int ptr); + private native void nativePause(int ptr); + private native void nativeResume(int ptr); private RemoteDisplay(Listener listener, Handler handler) { mListener = listener; @@ -87,6 +89,14 @@ public final class RemoteDisplay { dispose(false); } + public void pause() { + nativePause(mPtr); + } + + public void resume() { + nativeResume(mPtr); + } + private void dispose(boolean finalized) { if (mPtr != 0) { if (mGuard != null) { @@ -113,11 +123,11 @@ public final class RemoteDisplay { // Called from native. private void notifyDisplayConnected(final Surface surface, - final int width, final int height, final int flags) { + final int width, final int height, final int flags, final int session) { mHandler.post(new Runnable() { @Override public void run() { - mListener.onDisplayConnected(surface, width, height, flags); + mListener.onDisplayConnected(surface, width, height, flags, session); } }); } @@ -146,7 +156,8 @@ public final class RemoteDisplay { * Listener invoked when the remote display connection changes state. */ public interface Listener { - void onDisplayConnected(Surface surface, int width, int height, int flags); + void onDisplayConnected(Surface surface, + int width, int height, int flags, int session); void onDisplayDisconnected(); void onDisplayError(int error); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 091737d..22e3b98 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -584,7 +584,6 @@ public class DocumentsActivity extends Activity { SaveFragment.get(fm).setReplaceTarget(doc); } else if (mAction == ACTION_MANAGE) { // Open the document - // TODO: trampoline activity for launching downloaded APKs final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setData(doc.uri); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index b4bf563..8843e19 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -20,10 +20,13 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.UriMatcher; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; +import android.media.ExifInterface; import android.net.Uri; +import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; @@ -296,7 +299,6 @@ public class ExternalStorageProvider extends ContentProvider { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); - // TODO: offer as thumbnail final File file = docIdToFile(root, docId); return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode)); } @@ -307,6 +309,39 @@ public class ExternalStorageProvider extends ContentProvider { } @Override + public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) + throws FileNotFoundException { + if (opts == null || !opts.containsKey(DocumentsContract.EXTRA_THUMBNAIL_SIZE)) { + return super.openTypedAssetFile(uri, mimeTypeFilter, opts); + } + + switch (sMatcher.match(uri)) { + case URI_DOCS_ID: { + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); + + final File file = docIdToFile(root, docId); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( + file, ParcelFileDescriptor.MODE_READ_ONLY); + + try { + final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); + final long[] thumb = exif.getThumbnailRange(); + if (thumb != null) { + return new AssetFileDescriptor(pfd, thumb[0], thumb[1]); + } + } catch (IOException e) { + } + + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } + default: { + throw new UnsupportedOperationException("Unsupported Uri " + uri); + } + } + } + + @Override public Uri insert(Uri uri, ContentValues values) { switch (sMatcher.match(uri)) { case URI_DOCS_ID: { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index 2904f4c..d4b79b7 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -217,7 +217,7 @@ public class KeyguardHostView extends KeyguardViewBase { mCleanupAppWidgetsOnBootCompleted = true; return; } - if (!mSafeModeEnabled && !widgetsDisabledByDpm()) { + if (!mSafeModeEnabled && !widgetsDisabled()) { // Clean up appWidgetIds that are bound to lockscreen, but not actually used // This is only to clean up after another bug: we used to not call // deleteAppWidgetId when a user manually deleted a widget in keyguard. This code @@ -413,8 +413,11 @@ public class KeyguardHostView extends KeyguardViewBase { return disabledFeatures; } - private boolean widgetsDisabledByDpm() { - return (mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0; + private boolean widgetsDisabled() { + boolean disabledByDpm = + (mDisabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0; + boolean disabledByUser = !mLockPatternUtils.getWidgetsEnabled(); + return disabledByDpm || disabledByUser; } private boolean cameraDisabledByDpm() { @@ -1149,7 +1152,7 @@ public class KeyguardHostView extends KeyguardViewBase { } private void addDefaultWidgets() { - if (!mSafeModeEnabled && !widgetsDisabledByDpm()) { + if (!mSafeModeEnabled && !widgetsDisabled()) { LayoutInflater inflater = LayoutInflater.from(mContext); View addWidget = inflater.inflate(R.layout.keyguard_add_widget, this, false); mAppWidgetContainer.addWidget(addWidget, 0); @@ -1209,7 +1212,7 @@ public class KeyguardHostView extends KeyguardViewBase { } private void addWidgetsFromSettings() { - if (mSafeModeEnabled || widgetsDisabledByDpm()) { + if (mSafeModeEnabled || widgetsDisabled()) { return; } @@ -1246,7 +1249,6 @@ public class KeyguardHostView extends KeyguardViewBase { try { mAppWidgetManager.bindAppWidgetId(appWidgetId, defaultAppWidget); - } catch (IllegalArgumentException e) { Log.e(TAG, "Error when trying to bind default AppWidget: " + e); mAppWidgetHost.deleteAppWidgetId(appWidgetId); @@ -1254,6 +1256,7 @@ public class KeyguardHostView extends KeyguardViewBase { } return appWidgetId; } + public void checkAppWidgetConsistency() { // Since this method may bind a widget (which we can't do until boot completed) we // may have to defer it until after boot complete. @@ -1272,7 +1275,8 @@ public class KeyguardHostView extends KeyguardViewBase { if (!widgetPageExists) { final int insertPageIndex = getInsertPageIndex(); - final boolean userAddedWidgetsEnabled = !widgetsDisabledByDpm(); + final boolean userAddedWidgetsEnabled = !widgetsDisabled(); + boolean addedDefaultAppWidget = false; if (!mSafeModeEnabled) { diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java index e28a258..c5555c8 100644 --- a/services/java/com/android/server/LockSettingsService.java +++ b/services/java/com/android/server/LockSettingsService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.ActivityManagerNative; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -27,6 +28,9 @@ import static android.Manifest.permission.READ_PROFILE; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.media.AudioManager; +import android.media.AudioService; import android.os.Binder; import android.os.Environment; import android.os.RemoteException; @@ -37,6 +41,7 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import com.android.internal.widget.ILockSettings; @@ -391,7 +396,7 @@ public class LockSettingsService extends ILockSettings.Stub { private static final String TAG = "LockSettingsDB"; private static final String DATABASE_NAME = "locksettings.db"; - private static final int DATABASE_VERSION = 1; + private static final int DATABASE_VERSION = 2; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -424,7 +429,45 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { - // Nothing yet + int upgradeVersion = oldVersion; + if (upgradeVersion == 1) { + // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED} + // during upgrade based on whether each user previously had widgets in keyguard. + maybeEnableWidgetSettingForUsers(db); + upgradeVersion = 2; + } + + if (upgradeVersion != DATABASE_VERSION) { + Log.w(TAG, "Failed to upgrade database!"); + } + } + + private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) { + final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); + final ContentResolver cr = mContext.getContentResolver(); + final LockPatternUtils utils = new LockPatternUtils(mContext); + final List<UserInfo> users = um.getUsers(); + for (int i = 0; i < users.size(); i++) { + final int userId = users.get(i).id; + final boolean enabled = utils.hasWidgetsEnabledInKeyguard(userId); + Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled=" + + enabled + ", w[]=" + utils.getAppWidgets()); + loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled); + } + } + + private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement( + "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);"); + stmt.bindString(1, key); + stmt.bindLong(2, userId); + stmt.bindLong(3, value ? 1 : 0); + stmt.execute(); + } finally { + if (stmt != null) stmt.close(); + } } } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 8f4b6c2..4678b85 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -7644,7 +7644,7 @@ public final class ActivityManagerService extends ActivityManagerNative sCallerIdentity.set(new Identity( Binder.getCallingPid(), Binder.getCallingUid())); try { - pfd = cph.provider.openFile(null, uri, "r"); + pfd = cph.provider.openFile(null, uri, "r", null); } catch (FileNotFoundException e) { // do nothing; pfd will be returned null } finally { diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 659163c..249c8b0 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -497,6 +497,48 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } + @Override + public void pauseWifiDisplay() { + if (mContext.checkCallingPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" + + "permission to pause a wifi display session."); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestPauseLocked(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void resumeWifiDisplay() { + if (mContext.checkCallingPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" + + "permission to resume a wifi display session."); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestResumeLocked(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void disconnectWifiDisplay() { final long token = Binder.clearCallingIdentity(); diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index a9da30f..f7bbdf8 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.hardware.display.DisplayManager; import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplaySessionInfo; import android.hardware.display.WifiDisplayStatus; import android.media.RemoteDisplay; import android.os.Handler; @@ -93,6 +94,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { private WifiDisplay[] mDisplays = WifiDisplay.EMPTY_ARRAY; private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY; private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY; + private WifiDisplaySessionInfo mSessionInfo; private boolean mPendingStatusChangeBroadcast; private boolean mPendingNotificationUpdate; @@ -204,6 +206,36 @@ final class WifiDisplayAdapter extends DisplayAdapter { return false; } + public void requestPauseLocked() { + if (DEBUG) { + Slog.d(TAG, "requestPauseLocked"); + } + + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestPause(); + } + } + }); + } + + public void requestResumeLocked() { + if (DEBUG) { + Slog.d(TAG, "requestResumeLocked"); + } + + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestResume(); + } + } + }); + } + public void requestDisconnectLocked() { if (DEBUG) { Slog.d(TAG, "requestDisconnectedLocked"); @@ -267,7 +299,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { if (mCurrentStatus == null) { mCurrentStatus = new WifiDisplayStatus( mFeatureState, mScanState, mActiveDisplayState, - mActiveDisplay, mDisplays); + mActiveDisplay, mDisplays, mSessionInfo); } if (DEBUG) { @@ -580,6 +612,14 @@ final class WifiDisplayAdapter extends DisplayAdapter { } @Override + public void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo) { + synchronized (getSyncRoot()) { + mSessionInfo = sessionInfo; + scheduleStatusChangedBroadcastLocked(); + } + } + + @Override public void onDisplayChanged(WifiDisplay display) { synchronized (getSyncRoot()) { display = mPersistentDataStore.applyWifiDisplayAlias(display); diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java index 846a74d..cd201f5 100644 --- a/services/java/com/android/server/display/WifiDisplayController.java +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplaySessionInfo; import android.hardware.display.WifiDisplayStatus; import android.media.AudioManager; import android.media.RemoteDisplay; @@ -76,6 +77,7 @@ final class WifiDisplayController implements DumpUtils.Dump { private static final int MAX_THROUGHPUT = 50; private static final int CONNECTION_TIMEOUT_SECONDS = 60; private static final int RTSP_TIMEOUT_SECONDS = 15; + private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120; private static final int DISCOVER_PEERS_MAX_RETRIES = 10; private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; @@ -146,6 +148,10 @@ final class WifiDisplayController implements DumpUtils.Dump { private int mAdvertisedDisplayHeight; private int mAdvertisedDisplayFlags; + // Certification + private boolean mWifiDisplayCertMode; + private WifiP2pDevice mThisDevice; + public WifiDisplayController(Context context, Handler handler, Listener listener) { mContext = context; mHandler = handler; @@ -158,6 +164,7 @@ final class WifiDisplayController implements DumpUtils.Dump { intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); + intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler); ContentObserver settingsObserver = new ContentObserver(mHandler) { @@ -170,6 +177,8 @@ final class WifiDisplayController implements DumpUtils.Dump { final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver); updateSettings(); } @@ -177,6 +186,8 @@ final class WifiDisplayController implements DumpUtils.Dump { final ContentResolver resolver = mContext.getContentResolver(); mWifiDisplayOnSetting = Settings.Global.getInt(resolver, Settings.Global.WIFI_DISPLAY_ON, 0) != 0; + mWifiDisplayCertMode = Settings.Global.getInt(resolver, + Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0; updateWfdEnableState(); } @@ -223,6 +234,18 @@ final class WifiDisplayController implements DumpUtils.Dump { } } + public void requestPause() { + if (mRemoteDisplay != null) { + mRemoteDisplay.pause(); + } + } + + public void requestResume() { + if (mRemoteDisplay != null) { + mRemoteDisplay.resume(); + } + } + public void requestDisconnect() { disconnect(); } @@ -482,6 +505,7 @@ final class WifiDisplayController implements DumpUtils.Dump { Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); mDisconnectingDevice = mConnectedDevice; mConnectedDevice = null; + mConnectedDeviceGroupInfo = null; unadvertiseDisplay(); @@ -548,8 +572,12 @@ final class WifiDisplayController implements DumpUtils.Dump { return; // wait for asynchronous callback } - // Step 4. If we wanted to disconnect, then mission accomplished. + // Step 4. If we wanted to disconnect, or we're updating after starting an + // autonomous GO, then mission accomplished. if (mDesiredDevice == null) { + if (mWifiDisplayCertMode) { + mListener.onDisplaySessionInfo(getSessionInfo(mConnectedDeviceGroupInfo, 0)); + } unadvertiseDisplay(); return; // done } @@ -625,13 +653,18 @@ final class WifiDisplayController implements DumpUtils.Dump { mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { @Override public void onDisplayConnected(Surface surface, - int width, int height, int flags) { + int width, int height, int flags, int session) { if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { Slog.i(TAG, "Opened RTSP connection with Wifi display: " + mConnectedDevice.deviceName); mRemoteDisplayConnected = true; mHandler.removeCallbacks(mRtspTimeout); + if (mWifiDisplayCertMode) { + mListener.onDisplaySessionInfo( + getSessionInfo(mConnectedDeviceGroupInfo, session)); + } + final WifiDisplay display = createWifiDisplay(mConnectedDevice); advertiseDisplay(display, surface, width, height, flags); } @@ -658,8 +691,29 @@ final class WifiDisplayController implements DumpUtils.Dump { } }, mHandler); - mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); + // Use extended timeout value for certification, as some tests require user inputs + int rtspTimeout = mWifiDisplayCertMode ? + RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS; + + mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000); + } + } + + private WifiDisplaySessionInfo getSessionInfo(WifiP2pGroup info, int session) { + if (info == null) { + return null; + } + Inet4Address addr = getInterfaceAddress(info); + WifiDisplaySessionInfo sessionInfo = new WifiDisplaySessionInfo( + !info.getOwner().deviceAddress.equals(mThisDevice.deviceAddress), + session, + info.getOwner().deviceAddress + " " + info.getNetworkName(), + info.getPassphrase(), + (addr != null) ? addr.getHostAddress() : ""); + if (DEBUG) { + Slog.d(TAG, sessionInfo.toString()); } + return sessionInfo; } private void handleStateChanged(boolean enabled) { @@ -676,7 +730,7 @@ final class WifiDisplayController implements DumpUtils.Dump { private void handleConnectionChanged(NetworkInfo networkInfo) { mNetworkInfo = networkInfo; if (mWfdEnabled && networkInfo.isConnected()) { - if (mDesiredDevice != null) { + if (mDesiredDevice != null || mWifiDisplayCertMode) { mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { @Override public void onGroupInfoAvailable(WifiP2pGroup info) { @@ -698,6 +752,25 @@ final class WifiDisplayController implements DumpUtils.Dump { return; } + if (mWifiDisplayCertMode) { + boolean owner = info.getOwner().deviceAddress + .equals(mThisDevice.deviceAddress); + if (owner && info.getClientList().isEmpty()) { + // this is the case when we started Autonomous GO, + // and no client has connected, save group info + // and updateConnection() + mConnectingDevice = mDesiredDevice = null; + mConnectedDeviceGroupInfo = info; + updateConnection(); + } else if (mConnectingDevice == null && mDesiredDevice == null) { + // this is the case when we received an incoming connection + // from the sink, update both mConnectingDevice and mDesiredDevice + // then proceed to updateConnection() below + mConnectingDevice = mDesiredDevice = owner ? + info.getClientList().iterator().next() : info.getOwner(); + } + } + if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { Slog.i(TAG, "Connected to Wifi display: " + mConnectingDevice.deviceName); @@ -712,6 +785,7 @@ final class WifiDisplayController implements DumpUtils.Dump { }); } } else { + mConnectedDeviceGroupInfo = null; disconnect(); // After disconnection for a group, for some reason we have a tendency @@ -910,6 +984,13 @@ final class WifiDisplayController implements DumpUtils.Dump { } handleConnectionChanged(networkInfo); + } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) { + mThisDevice = (WifiP2pDevice) intent.getParcelableExtra( + WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); + if (DEBUG) { + Slog.d(TAG, "Received WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: mThisDevice= " + + mThisDevice); + } } } }; @@ -928,6 +1009,7 @@ final class WifiDisplayController implements DumpUtils.Dump { void onDisplayChanged(WifiDisplay display); void onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags); + void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo); void onDisplayDisconnected(); } } diff --git a/services/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/jni/com_android_server_location_FlpHardwareProvider.cpp index 9465a68..b403ee6 100644 --- a/services/jni/com_android_server_location_FlpHardwareProvider.cpp +++ b/services/jni/com_android_server_location_FlpHardwareProvider.cpp @@ -217,6 +217,8 @@ static void TranslateFromObject( } // TODO: wire sources_used if Location class exposes them + + env->DeleteLocalRef(locationClass); } /* @@ -257,6 +259,8 @@ static void TranslateFromObject( jmethodID getFlags = env->GetMethodID(batchOptionsClass, "getFlags", "()I"); batchOptions.flags = env->CallIntMethod(batchOptionsObject, getFlags); + + env->DeleteLocalRef(batchOptionsClass); } /* @@ -326,6 +330,8 @@ static void TranslateGeofenceFromGeofenceHardwareRequestParcelable( options->last_transition = env->CallIntMethod(geofenceRequestObject, getLastTransition); // TODO: set data.sources_to_use when available + + env->DeleteLocalRef(geofenceRequestClass); } /* @@ -408,6 +414,8 @@ static void TranslateToObject(const FlpLocation* location, jobject& locationObje } // TODO: wire FlpLocation::sources_used when needed + + sCallbackEnv->DeleteLocalRef(locationClass); } /* @@ -430,6 +438,8 @@ static void TranslateToObjectArray( sCallbackEnv->SetObjectArrayElement(locationsArray, i, locationObject); sCallbackEnv->DeleteLocalRef(locationObject); } + + sCallbackEnv->DeleteLocalRef(locationClass); } static void LocationCallback(int32_t locationsCount, FlpLocation** locations) { @@ -455,6 +465,10 @@ static void LocationCallback(int32_t locationsCount, FlpLocation** locations) { locationsArray ); CheckExceptions(sCallbackEnv, __FUNCTION__); + + if(locationsArray != NULL) { + sCallbackEnv->DeleteLocalRef(locationsArray); + } } static void AcquireWakelock() { @@ -522,6 +536,10 @@ static void GeofenceTransitionCallback( sourcesUsed ); CheckExceptions(sCallbackEnv, __FUNCTION__); + + if(locationObject != NULL) { + sCallbackEnv->DeleteLocalRef(locationObject); + } } static void GeofenceMonitorStatusCallback( @@ -545,6 +563,10 @@ static void GeofenceMonitorStatusCallback( locationObject ); CheckExceptions(sCallbackEnv, __FUNCTION__); + + if(locationObject != NULL) { + sCallbackEnv->DeleteLocalRef(locationObject); + } } static void GeofenceAddCallback(int32_t geofenceId, int32_t result) { @@ -843,6 +865,7 @@ static void AddGeofences( jobject geofenceObject = env->GetObjectArrayElement(geofenceRequestsArray, i); TranslateGeofenceFromGeofenceHardwareRequestParcelable(env, geofenceObject, geofences[i]); + env->DeleteLocalRef(geofenceObject); } sFlpGeofencingInterface->add_geofences(geofenceRequestsCount, &geofences); diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java index 373f24a..596ea0a 100644 --- a/test-runner/src/android/test/mock/MockContentProvider.java +++ b/test-runner/src/android/test/mock/MockContentProvider.java @@ -83,13 +83,15 @@ public class MockContentProvider extends ContentProvider { } @Override - public AssetFileDescriptor openAssetFile(String callingPackage, Uri url, String mode) + public AssetFileDescriptor openAssetFile( + String callingPackage, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { return MockContentProvider.this.openAssetFile(url, mode); } @Override - public ParcelFileDescriptor openFile(String callingPackage, Uri url, String mode) + public ParcelFileDescriptor openFile( + String callingPackage, Uri url, String mode, ICancellationSignal signal) throws RemoteException, FileNotFoundException { return MockContentProvider.this.openFile(url, mode); } @@ -126,7 +128,7 @@ public class MockContentProvider extends ContentProvider { @Override public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri url, - String mimeType, Bundle opts) + String mimeType, Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException { return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts); } diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java index 2b4fce6..b14ce49 100644 --- a/test-runner/src/android/test/mock/MockIContentProvider.java +++ b/test-runner/src/android/test/mock/MockIContentProvider.java @@ -61,11 +61,13 @@ public class MockIContentProvider implements IContentProvider { throw new UnsupportedOperationException("unimplemented mock method"); } - public ParcelFileDescriptor openFile(String callingPackage, Uri url, String mode) { + public ParcelFileDescriptor openFile( + String callingPackage, Uri url, String mode, ICancellationSignal signal) { throw new UnsupportedOperationException("unimplemented mock method"); } - public AssetFileDescriptor openAssetFile(String callingPackage, Uri uri, String mode) { + public AssetFileDescriptor openAssetFile( + String callingPackage, Uri uri, String mode, ICancellationSignal signal) { throw new UnsupportedOperationException("unimplemented mock method"); } @@ -104,7 +106,7 @@ public class MockIContentProvider implements IContentProvider { } public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri url, String mimeType, - Bundle opts) throws RemoteException, FileNotFoundException { + Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException { throw new UnsupportedOperationException("unimplemented mock method"); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index 4aea38f..688cc87 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -81,14 +81,16 @@ public final class BridgeContentProvider implements IContentProvider { } @Override - public AssetFileDescriptor openAssetFile(String callingPackage, Uri arg0, String arg1) + public AssetFileDescriptor openAssetFile( + String callingPackage, Uri arg0, String arg1, ICancellationSignal signal) throws RemoteException, FileNotFoundException { // TODO Auto-generated method stub return null; } @Override - public ParcelFileDescriptor openFile(String callingPackage, Uri arg0, String arg1) + public ParcelFileDescriptor openFile( + String callingPackage, Uri arg0, String arg1, ICancellationSignal signal) throws RemoteException, FileNotFoundException { // TODO Auto-generated method stub return null; @@ -122,7 +124,7 @@ public final class BridgeContentProvider implements IContentProvider { @Override public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri arg0, String arg1, - Bundle arg2) throws RemoteException, FileNotFoundException { + Bundle arg2, ICancellationSignal signal) throws RemoteException, FileNotFoundException { // TODO Auto-generated method stub return null; } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 79c1163..d3342dd 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -619,6 +619,37 @@ public class WifiNative { return doBooleanCommand("P2P_LISTEN " + timeout); } + public boolean p2pExtListen(boolean enable, int period, int interval) { + if (enable && interval < period) { + return false; + } + return doBooleanCommand("P2P_EXT_LISTEN" + + (enable ? (" " + period + " " + interval) : "")); + } + + public boolean p2pSetChannel(int lc, int oc) { + if (DBG) Log.d(mTAG, "p2pSetChannel: lc="+lc+", oc="+oc); + + if (lc >=1 && lc <= 11) { + if (!doBooleanCommand("P2P_SET listen_channel " + lc)) { + return false; + } + } else if (lc != 0) { + return false; + } + + if (oc >= 1 && oc <= 165 ) { + int freq = (oc <= 14 ? 2407 : 5000) + oc * 5; + return doBooleanCommand("P2P_SET disallow_freq 1000-" + + (freq - 5) + "," + (freq + 5) + "-6000"); + } else if (oc == 0) { + /* oc==0 disables "P2P_SET disallow_freq" (enables all freqs) */ + return doBooleanCommand("P2P_SET disallow_freq \"\""); + } + + return false; + } + public boolean p2pFlush() { return doBooleanCommand("P2P_FLUSH"); } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 737ab91..4988b92 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -30,6 +30,7 @@ import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo; import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Handler; import android.os.Looper; @@ -430,6 +431,28 @@ public class WifiP2pManager { /** @hide */ public static final int START_WPS_SUCCEEDED = BASE + 64; + /** @hide */ + public static final int START_LISTEN = BASE + 65; + /** @hide */ + public static final int START_LISTEN_FAILED = BASE + 66; + /** @hide */ + public static final int START_LISTEN_SUCCEEDED = BASE + 67; + + /** @hide */ + public static final int STOP_LISTEN = BASE + 68; + /** @hide */ + public static final int STOP_LISTEN_FAILED = BASE + 69; + /** @hide */ + public static final int STOP_LISTEN_SUCCEEDED = BASE + 70; + + /** @hide */ + public static final int SET_CHANNEL = BASE + 71; + /** @hide */ + public static final int SET_CHANNEL_FAILED = BASE + 72; + /** @hide */ + public static final int SET_CHANNEL_SUCCEEDED = BASE + 73; + + /** * Create a new WifiP2pManager instance. Applications use * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve @@ -667,6 +690,9 @@ public class WifiP2pManager { case DELETE_PERSISTENT_GROUP_FAILED: case SET_WFD_INFO_FAILED: case START_WPS_FAILED: + case START_LISTEN_FAILED: + case STOP_LISTEN_FAILED: + case SET_CHANNEL_FAILED: if (listener != null) { ((ActionListener) listener).onFailure(message.arg1); } @@ -689,6 +715,9 @@ public class WifiP2pManager { case DELETE_PERSISTENT_GROUP_SUCCEEDED: case SET_WFD_INFO_SUCCEEDED: case START_WPS_SUCCEEDED: + case START_LISTEN_SUCCEEDED: + case STOP_LISTEN_SUCCEEDED: + case SET_CHANNEL_SUCCEEDED: if (listener != null) { ((ActionListener) listener).onSuccess(); } @@ -955,6 +984,22 @@ public class WifiP2pManager { c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener)); } + /** @hide */ + public void listen(Channel c, boolean enable, ActionListener listener) { + checkChannel(c); + c.mAsyncChannel.sendMessage(enable ? START_LISTEN : STOP_LISTEN, + 0, c.putListener(listener)); + } + + /** @hide */ + public void setWifiP2pChannels(Channel c, int lc, int oc, ActionListener listener) { + checkChannel(c); + Bundle p2pChannels = new Bundle(); + p2pChannels.putInt("lc", lc); + p2pChannels.putInt("oc", oc); + c.mAsyncChannel.sendMessage(SET_CHANNEL, 0, c.putListener(listener), p2pChannels); + } + /** * Start a Wi-Fi Protected Setup (WPS) session. * diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java index 63b94a2..05196b8 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -48,6 +48,7 @@ import android.net.wifi.p2p.nsd.WifiP2pServiceInfo; import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Handler; @@ -628,6 +629,9 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case DhcpStateMachine.CMD_ON_QUIT: case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT: case SET_MIRACAST_MODE: + case WifiP2pManager.START_LISTEN: + case WifiP2pManager.STOP_LISTEN: + case WifiP2pManager.SET_CHANNEL: break; case WifiStateMachine.CMD_ENABLE_P2P: // Enable is lazy and has no response @@ -729,7 +733,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.START_WPS_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; - default: + case WifiP2pManager.START_LISTEN: + replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); + break; + case WifiP2pManager.STOP_LISTEN: + replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED, + WifiP2pManager.P2P_UNSUPPORTED); + break; + + default: return NOT_HANDLED; } return HANDLED; @@ -1022,6 +1035,35 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case SET_MIRACAST_MODE: mWifiNative.setMiracastMode(message.arg1); break; + case WifiP2pManager.START_LISTEN: + if (DBG) logd(getName() + " start listen mode"); + mWifiNative.p2pFlush(); + if (mWifiNative.p2pExtListen(true, 500, 500)) { + replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED); + } + break; + case WifiP2pManager.STOP_LISTEN: + if (DBG) logd(getName() + " stop listen mode"); + if (mWifiNative.p2pExtListen(false, 0, 0)) { + replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED); + } + mWifiNative.p2pFlush(); + break; + case WifiP2pManager.SET_CHANNEL: + Bundle p2pChannels = (Bundle) message.obj; + int lc = p2pChannels.getInt("lc", 0); + int oc = p2pChannels.getInt("oc", 0); + if (DBG) logd(getName() + " set listen and operating channel"); + if (mWifiNative.p2pSetChannel(lc, oc)) { + replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED); + } + break; default: return NOT_HANDLED; } @@ -1171,6 +1213,35 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mWifiNative.p2pGroupRemove(mGroup.getInterface()); } break; + case WifiP2pManager.START_LISTEN: + if (DBG) logd(getName() + " start listen mode"); + mWifiNative.p2pFlush(); + if (mWifiNative.p2pExtListen(true, 500, 500)) { + replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED); + } + break; + case WifiP2pManager.STOP_LISTEN: + if (DBG) logd(getName() + " stop listen mode"); + if (mWifiNative.p2pExtListen(false, 0, 0)) { + replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED); + } + mWifiNative.p2pFlush(); + break; + case WifiP2pManager.SET_CHANNEL: + Bundle p2pChannels = (Bundle) message.obj; + int lc = p2pChannels.getInt("lc", 0); + int oc = p2pChannels.getInt("oc", 0); + if (DBG) logd(getName() + " set listen and operating channel"); + if (mWifiNative.p2pSetChannel(lc, oc)) { + replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED); + } else { + replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED); + } + break; default: return NOT_HANDLED; } |