diff options
Diffstat (limited to 'core/java')
43 files changed, 3922 insertions, 352 deletions
diff --git a/core/java/android/bluetooth/BluetoothAssignedNumbers.java b/core/java/android/bluetooth/BluetoothAssignedNumbers.java index 580e9ff..124bdc1 100644 --- a/core/java/android/bluetooth/BluetoothAssignedNumbers.java +++ b/core/java/android/bluetooth/BluetoothAssignedNumbers.java @@ -513,6 +513,656 @@ public class BluetoothAssignedNumbers { public static final int RIVIERAWAVES = 0x0060; /* + * RDA Microelectronics. + */ + public static final int RDA_MICROELECTRONICS = 0x0061; + + /* + * Gibson Guitars. + */ + public static final int GIBSON_GUITARS = 0x0062; + + /* + * MiCommand Inc. + */ + public static final int MICOMMAND = 0x0063; + + /* + * Band XI International, LLC. + */ + public static final int BAND_XI_INTERNATIONAL = 0x0064; + + /* + * Hewlett-Packard Company. + */ + public static final int HEWLETT_PACKARD = 0x0065; + + /* + * 9Solutions Oy. + */ + public static final int NINE_SOLUTIONS = 0x0066; + + /* + * GN Netcom A/S. + */ + public static final int GN_NETCOM = 0x0067; + + /* + * General Motors. + */ + public static final int GENERAL_MOTORS = 0x0068; + + /* + * A&D Engineering, Inc. + */ + public static final int A_AND_D_ENGINEERING = 0x0069; + + /* + * MindTree Ltd. + */ + public static final int MINDTREE = 0x006A; + + /* + * Polar Electro OY. + */ + public static final int POLAR_ELECTRO = 0x006B; + + /* + * Beautiful Enterprise Co., Ltd. + */ + public static final int BEAUTIFUL_ENTERPRISE = 0x006C; + + /* + * BriarTek, Inc. + */ + public static final int BRIARTEK = 0x006D; + + /* + * Summit Data Communications, Inc. + */ + public static final int SUMMIT_DATA_COMMUNICATIONS = 0x006E; + + /* + * Sound ID. + */ + public static final int SOUND_ID = 0x006F; + + /* + * Monster, LLC. + */ + public static final int MONSTER = 0x0070; + + /* + * connectBlue AB. + */ + public static final int CONNECTBLUE = 0x0071; + + /* + * ShangHai Super Smart Electronics Co. Ltd. + */ + public static final int SHANGHAI_SUPER_SMART_ELECTRONICS = 0x0072; + + /* + * Group Sense Ltd. + */ + public static final int GROUP_SENSE = 0x0073; + + /* + * Zomm, LLC. + */ + public static final int ZOMM = 0x0074; + + /* + * Samsung Electronics Co. Ltd. + */ + public static final int SAMSUNG_ELECTRONICS = 0x0075; + + /* + * Creative Technology Ltd. + */ + public static final int CREATIVE_TECHNOLOGY = 0x0076; + + /* + * Laird Technologies. + */ + public static final int LAIRD_TECHNOLOGIES = 0x0077; + + /* + * Nike, Inc. + */ + public static final int NIKE = 0x0078; + + /* + * lesswire AG. + */ + public static final int LESSWIRE = 0x0079; + + /* + * MStar Semiconductor, Inc. + */ + public static final int MSTAR_SEMICONDUCTOR = 0x007A; + + /* + * Hanlynn Technologies. + */ + public static final int HANLYNN_TECHNOLOGIES = 0x007B; + + /* + * A & R Cambridge. + */ + public static final int A_AND_R_CAMBRIDGE = 0x007C; + + /* + * Seers Technology Co. Ltd. + */ + public static final int SEERS_TECHNOLOGY = 0x007D; + + /* + * Sports Tracking Technologies Ltd. + */ + public static final int SPORTS_TRACKING_TECHNOLOGIES = 0x007E; + + /* + * Autonet Mobile. + */ + public static final int AUTONET_MOBILE = 0x007F; + + /* + * DeLorme Publishing Company, Inc. + */ + public static final int DELORME_PUBLISHING_COMPANY = 0x0080; + + /* + * WuXi Vimicro. + */ + public static final int WUXI_VIMICRO = 0x0081; + + /* + * Sennheiser Communications A/S. + */ + public static final int SENNHEISER_COMMUNICATIONS = 0x0082; + + /* + * TimeKeeping Systems, Inc. + */ + public static final int TIMEKEEPING_SYSTEMS = 0x0083; + + /* + * Ludus Helsinki Ltd. + */ + public static final int LUDUS_HELSINKI = 0x0084; + + /* + * BlueRadios, Inc. + */ + public static final int BLUERADIOS = 0x0085; + + /* + * equinox AG. + */ + public static final int EQUINOX_AG = 0x0086; + + /* + * Garmin International, Inc. + */ + public static final int GARMIN_INTERNATIONAL = 0x0087; + + /* + * Ecotest. + */ + public static final int ECOTEST = 0x0088; + + /* + * GN ReSound A/S. + */ + public static final int GN_RESOUND = 0x0089; + + /* + * Jawbone. + */ + public static final int JAWBONE = 0x008A; + + /* + * Topcorn Positioning Systems, LLC. + */ + public static final int TOPCORN_POSITIONING_SYSTEMS = 0x008B; + + /* + * Qualcomm Labs, Inc. + */ + public static final int QUALCOMM_LABS = 0x008C; + + /* + * Zscan Software. + */ + public static final int ZSCAN_SOFTWARE = 0x008D; + + /* + * Quintic Corp. + */ + public static final int QUINTIC = 0x008E; + + /* + * Stollman E+V GmbH. + */ + public static final int STOLLMAN_E_PLUS_V = 0x008F; + + /* + * Funai Electric Co., Ltd. + */ + public static final int FUNAI_ELECTRIC = 0x0090; + + /* + * Advanced PANMOBIL Systems GmbH & Co. KG. + */ + public static final int ADVANCED_PANMOBIL_SYSTEMS = 0x0091; + + /* + * ThinkOptics, Inc. + */ + public static final int THINKOPTICS = 0x0092; + + /* + * Universal Electronics, Inc. + */ + public static final int UNIVERSAL_ELECTRONICS = 0x0093; + + /* + * Airoha Technology Corp. + */ + public static final int AIROHA_TECHNOLOGY = 0x0094; + + /* + * NEC Lighting, Ltd. + */ + public static final int NEC_LIGHTING = 0x0095; + + /* + * ODM Technology, Inc. + */ + public static final int ODM_TECHNOLOGY = 0x0096; + + /* + * Bluetrek Technologies Limited. + */ + public static final int BLUETREK_TECHNOLOGIES = 0x0097; + + /* + * zer01.tv GmbH. + */ + public static final int ZER01_TV = 0x0098; + + /* + * i.Tech Dynamic Global Distribution Ltd. + */ + public static final int I_TECH_DYNAMIC_GLOBAL_DISTRIBUTION = 0x0099; + + /* + * Alpwise. + */ + public static final int ALPWISE = 0x009A; + + /* + * Jiangsu Toppower Automotive Electronics Co., Ltd. + */ + public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS = 0x009B; + + /* + * Colorfy, Inc. + */ + public static final int COLORFY = 0x009C; + + /* + * Geoforce Inc. + */ + public static final int GEOFORCE = 0x009D; + + /* + * Bose Corporation. + */ + public static final int BOSE = 0x009E; + + /* + * Suunto Oy. + */ + public static final int SUUNTO = 0x009F; + + /* + * Kensington Computer Products Group. + */ + public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 0x00A0; + + /* + * SR-Medizinelektronik. + */ + public static final int SR_MEDIZINELEKTRONIK = 0x00A1; + + /* + * Vertu Corporation Limited. + */ + public static final int VERTU = 0x00A2; + + /* + * Meta Watch Ltd. + */ + public static final int META_WATCH = 0x00A3; + + /* + * LINAK A/S. + */ + public static final int LINAK = 0x00A4; + + /* + * OTL Dynamics LLC. + */ + public static final int OTL_DYNAMICS = 0x00A5; + + /* + * Panda Ocean Inc. + */ + public static final int PANDA_OCEAN = 0x00A6; + + /* + * Visteon Corporation. + */ + public static final int VISTEON = 0x00A7; + + /* + * ARP Devices Limited. + */ + public static final int ARP_DEVICES = 0x00A8; + + /* + * Magneti Marelli S.p.A. + */ + public static final int MAGNETI_MARELLI = 0x00A9; + + /* + * CAEN RFID srl. + */ + public static final int CAEN_RFID = 0x00AA; + + /* + * Ingenieur-Systemgruppe Zahn GmbH. + */ + public static final int INGENIEUR_SYSTEMGRUPPE_ZAHN = 0x00AB; + + /* + * Green Throttle Games. + */ + public static final int GREEN_THROTTLE_GAMES = 0x00AC; + + /* + * Peter Systemtechnik GmbH. + */ + public static final int PETER_SYSTEMTECHNIK = 0x00AD; + + /* + * Omegawave Oy. + */ + public static final int OMEGAWAVE = 0x00AE; + + /* + * Cinetix. + */ + public static final int CINETIX = 0x00AF; + + /* + * Passif Semiconductor Corp. + */ + public static final int PASSIF_SEMICONDUCTOR = 0x00B0; + + /* + * Saris Cycling Group, Inc. + */ + public static final int SARIS_CYCLING_GROUP = 0x00B1; + + /* + * Bekey A/S. + */ + public static final int BEKEY = 0x00B2; + + /* + * Clarinox Technologies Pty. Ltd. + */ + public static final int CLARINOX_TECHNOLOGIES = 0x00B3; + + /* + * BDE Technology Co., Ltd. + */ + public static final int BDE_TECHNOLOGY = 0x00B4; + + /* + * Swirl Networks. + */ + public static final int SWIRL_NETWORKS = 0x00B5; + + /* + * Meso international. + */ + public static final int MESO_INTERNATIONAL = 0x00B6; + + /* + * TreLab Ltd. + */ + public static final int TRELAB = 0x00B7; + + /* + * Qualcomm Innovation Center, Inc. (QuIC). + */ + public static final int QUALCOMM_INNOVATION_CENTER = 0x00B8; + + /* + * Johnson Controls, Inc. + */ + public static final int JOHNSON_CONTROLS = 0x00B9; + + /* + * Starkey Laboratories Inc. + */ + public static final int STARKEY_LABORATORIES = 0x00BA; + + /* + * S-Power Electronics Limited. + */ + public static final int S_POWER_ELECTRONICS = 0x00BB; + + /* + * Ace Sensor Inc. + */ + public static final int ACE_SENSOR = 0x00BC; + + /* + * Aplix Corporation. + */ + public static final int APLIX = 0x00BD; + + /* + * AAMP of America. + */ + public static final int AAMP_OF_AMERICA = 0x00BE; + + /* + * Stalmart Technology Limited. + */ + public static final int STALMART_TECHNOLOGY = 0x00BF; + + /* + * AMICCOM Electronics Corporation. + */ + public static final int AMICCOM_ELECTRONICS = 0x00C0; + + /* + * Shenzhen Excelsecu Data Technology Co.,Ltd. + */ + public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY = 0x00C1; + + /* + * Geneq Inc. + */ + public static final int GENEQ = 0x00C2; + + /* + * adidas AG. + */ + public static final int ADIDAS = 0x00C3; + + /* + * LG Electronics. + */ + public static final int LG_ELECTRONICS = 0x00C4; + + /* + * Onset Computer Corporation. + */ + public static final int ONSET_COMPUTER = 0x00C5; + + /* + * Selfly BV. + */ + public static final int SELFLY = 0x00C6; + + /* + * Quuppa Oy. + */ + public static final int QUUPPA = 0x00C7; + + /* + * GeLo Inc. + */ + public static final int GELO = 0x00C8; + + /* + * Evluma. + */ + public static final int EVLUMA = 0x00C9; + + /* + * MC10. + */ + public static final int MC10 = 0x00CA; + + /* + * Binauric SE. + */ + public static final int BINAURIC = 0x00CB; + + /* + * Beats Electronics. + */ + public static final int BEATS_ELECTRONICS = 0x00CC; + + /* + * Microchip Technology Inc. + */ + public static final int MICROCHIP_TECHNOLOGY = 0x00CD; + + /* + * Elgato Systems GmbH. + */ + public static final int ELGATO_SYSTEMS = 0x00CE; + + /* + * ARCHOS SA. + */ + public static final int ARCHOS = 0x00CF; + + /* + * Dexcom, Inc. + */ + public static final int DEXCOM = 0x00D0; + + /* + * Polar Electro Europe B.V. + */ + public static final int POLAR_ELECTRO_EUROPE = 0x00D1; + + /* + * Dialog Semiconductor B.V. + */ + public static final int DIALOG_SEMICONDUCTOR = 0x00D2; + + /* + * Taixingbang Technology (HK) Co,. LTD. + */ + public static final int TAIXINGBANG_TECHNOLOGY = 0x00D3; + + /* + * Kawantech. + */ + public static final int KAWANTECH = 0x00D4; + + /* + * Austco Communication Systems. + */ + public static final int AUSTCO_COMMUNICATION_SYSTEMS = 0x00D5; + + /* + * Timex Group USA, Inc. + */ + public static final int TIMEX_GROUP_USA = 0x00D6; + + /* + * Qualcomm Technologies, Inc. + */ + public static final int QUALCOMM_TECHNOLOGIES = 0x00D7; + + /* + * Qualcomm Connected Experiences, Inc. + */ + public static final int QUALCOMM_CONNECTED_EXPERIENCES = 0x00D8; + + /* + * Voyetra Turtle Beach. + */ + public static final int VOYETRA_TURTLE_BEACH = 0x00D9; + + /* + * txtr GmbH. + */ + public static final int TXTR = 0x00DA; + + /* + * Biosentronics. + */ + public static final int BIOSENTRONICS = 0x00DB; + + /* + * Procter & Gamble. + */ + public static final int PROCTER_AND_GAMBLE = 0x00DC; + + /* + * Hosiden Corporation. + */ + public static final int HOSIDEN = 0x00DD; + + /* + * Muzik LLC. + */ + public static final int MUZIK = 0x00DE; + + /* + * Misfit Wearables Corp. + */ + public static final int MISFIT_WEARABLES = 0x00DF; + + /* + * Google. + */ + public static final int GOOGLE = 0x00E0; + + /* + * Danlers Ltd. + */ + public static final int DANLERS = 0x00E1; + + /* + * Semilink Inc. + */ + public static final int SEMILINK = 0x00E2; + + /* * You can't instantiate one of these. */ private BluetoothAssignedNumbers() { diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 5a5764d..1962514 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -193,6 +193,11 @@ public final class BluetoothHeadset implements BluetoothProfile { "android.bluetooth.headset.intent.category.companyid"; /** + * A vendor-specific command for unsolicited result code. + */ + public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; + + /** * Headset state when SCO audio is not connected. * This state can be one of * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of @@ -840,6 +845,46 @@ public final class BluetoothHeadset implements BluetoothProfile { } } + /** + * Sends a vendor-specific unsolicited result code to the headset. + * + * <p>The actual string to be sent is <code>command + ": " + arg</code>. + * For example, if {@code command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} + * is {@code "0"}, the string <code>"+ANDROID: 0"</code> will be sent. + * + * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param device Bluetooth headset. + * @param command A vendor-specific command. + * @param arg The argument that will be attached to the command. + * @return {@code false} if there is no headset connected, or if the command is not an allowed + * vendor-specific unsolicited result code, or on error. {@code true} otherwise. + * @throws IllegalArgumentException if {@code command} is {@code null}. + */ + public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, + String arg) { + if (DBG) { + log("sendVendorSpecificResultCode()"); + } + if (command == null) { + throw new IllegalArgumentException("command is null"); + } + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.sendVendorSpecificResultCode(device, command, arg); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) { + Log.w(TAG, "Proxy not attached to service"); + } + return false; + } + private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 285eea7..524ca6f 100755 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -35,6 +35,9 @@ interface IBluetoothHeadset { boolean startVoiceRecognition(in BluetoothDevice device); boolean stopVoiceRecognition(in BluetoothDevice device); boolean isAudioConnected(in BluetoothDevice device); + boolean sendVendorSpecificResultCode(in BluetoothDevice device, + in String command, + in String arg); // APIs that can be made public in future int getBatteryUsageHint(in BluetoothDevice device); 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/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index ee199bc..64e4dc9 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -16,21 +16,26 @@ package android.hardware.camera2.impl; -import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.ICameraDeviceUser; -import android.hardware.camera2.ICameraDeviceCallbacks; +import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; + import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraProperties; import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.utils.CameraRuntimeException; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.utils.CameraBinderDecorator; +import android.hardware.camera2.utils.CameraRuntimeException; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.util.SparseArray; import android.view.Surface; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Stack; @@ -54,6 +59,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { new HashMap<Integer, CaptureListenerHolder>(); private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>(); + // Map stream IDs to Surfaces + private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); private final String mCameraId; @@ -94,18 +101,49 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void configureOutputs(List<Surface> outputs) throws CameraAccessException { synchronized (mLock) { - // TODO: delete outputs that aren't in this list that were configured previously - for (Surface s : outputs) { - try { + HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create + List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete + + // Determine which streams need to be created, which to be deleted + for (int i = 0; i < mConfiguredOutputs.size(); ++i) { + int streamId = mConfiguredOutputs.keyAt(i); + Surface s = mConfiguredOutputs.valueAt(i); + + if (!outputs.contains(s)) { + deleteList.add(streamId); + } else { + addSet.remove(s); // Don't create a stream previously created + } + } + + try { + // TODO: mRemoteDevice.beginConfigure + + // Delete all streams first (to free up HW resources) + for (Integer streamId : deleteList) { + mRemoteDevice.deleteStream(streamId); + mConfiguredOutputs.delete(streamId); + } + + // Add all new streams + for (Surface s : addSet) { // TODO: remove width,height,format since we are ignoring // it. - mRemoteDevice.createStream(0, 0, 0, s); - } catch (CameraRuntimeException e) { - throw e.asChecked(); - } catch (RemoteException e) { - // impossible - return; + int streamId = mRemoteDevice.createStream(0, 0, 0, s); + mConfiguredOutputs.put(streamId, s); } + + // TODO: mRemoteDevice.endConfigure + } catch (CameraRuntimeException e) { + if (e.getReason() == CAMERA_IN_USE) { + throw new IllegalStateException("The camera is currently busy." + + " You must call waitUntilIdle before trying to reconfigure."); + } + + throw e.asChecked(); + } catch (RemoteException e) { + // impossible + return; } } } 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/net/LinkInfo.java b/core/java/android/net/LinkInfo.java index 98e8f35..47b8a95 100644 --- a/core/java/android/net/LinkInfo.java +++ b/core/java/android/net/LinkInfo.java @@ -30,7 +30,7 @@ import android.os.Parcelable; */ public class LinkInfo implements Parcelable { - public static final int UNKNOWN = Integer.MAX_VALUE; + public static final int UNKNOWN = -1; public static final int NORMALIZED_MIN_SIGNAL_STRENGTH = 0; @@ -43,8 +43,8 @@ public class LinkInfo implements Parcelable public int mNormalizedSignalStrength = UNKNOWN; - public int mPacketCount = UNKNOWN; - public int mPacketErrorCount = UNKNOWN; + public long mPacketCount = UNKNOWN; + public long mPacketErrorCount = UNKNOWN; public int mTheoreticalTxBandwidth = UNKNOWN; public int mTheoreticalRxBandwidth = UNKNOWN; public int mTheoreticalLatency = UNKNOWN; @@ -82,8 +82,8 @@ public class LinkInfo implements Parcelable dest.writeInt(objectType); dest.writeInt(mNetworkType); dest.writeInt(mNormalizedSignalStrength); - dest.writeInt(mPacketCount); - dest.writeInt(mPacketErrorCount); + dest.writeLong(mPacketCount); + dest.writeLong(mPacketErrorCount); dest.writeInt(mTheoreticalTxBandwidth); dest.writeInt(mTheoreticalRxBandwidth); dest.writeInt(mTheoreticalLatency); @@ -116,8 +116,8 @@ public class LinkInfo implements Parcelable protected void initializeFromParcel(Parcel in) { mNetworkType = in.readInt(); mNormalizedSignalStrength = in.readInt(); - mPacketCount = in.readInt(); - mPacketErrorCount = in.readInt(); + mPacketCount = in.readLong(); + mPacketErrorCount = in.readLong(); mTheoreticalTxBandwidth = in.readInt(); mTheoreticalRxBandwidth = in.readInt(); mTheoreticalLatency = in.readInt(); diff --git a/core/java/android/net/SamplingDataTracker.java b/core/java/android/net/SamplingDataTracker.java index b5dc140..ac24930 100644 --- a/core/java/android/net/SamplingDataTracker.java +++ b/core/java/android/net/SamplingDataTracker.java @@ -37,12 +37,12 @@ public class SamplingDataTracker public static class SamplingSnapshot { - public int mTxByteCount; - public int mRxByteCount; - public int mTxPacketCount; - public int mRxPacketCount; - public int mTxPacketErrorCount; - public int mRxPacketErrorCount; + public long mTxByteCount; + public long mRxByteCount; + public long mTxPacketCount; + public long mRxPacketCount; + public long mTxPacketErrorCount; + public long mRxPacketErrorCount; public long mTimestamp; } @@ -76,32 +76,37 @@ public class SamplingDataTracker if (DBG) Slog.d(TAG, "Found data for interface " + currentIface); if (mapIfaceToSample.containsKey(currentIface)) { - SamplingSnapshot ss = new SamplingSnapshot(); - - ss.mTxByteCount = Integer.parseInt(tokens[1]); - ss.mTxPacketCount = Integer.parseInt(tokens[2]); - ss.mTxPacketErrorCount = Integer.parseInt(tokens[3]); - ss.mRxByteCount = Integer.parseInt(tokens[9]); - ss.mRxPacketCount = Integer.parseInt(tokens[10]); - ss.mRxPacketErrorCount = Integer.parseInt(tokens[11]); - - ss.mTimestamp = SystemClock.elapsedRealtime(); - - if (DBG) { - Slog.d(TAG, "Interface = " + currentIface); - Slog.d(TAG, "ByteCount = " + String.valueOf(ss.mTxByteCount)); - Slog.d(TAG, "TxPacketCount = " + String.valueOf(ss.mTxPacketCount)); - Slog.d(TAG, "TxPacketErrorCount = " - + String.valueOf(ss.mTxPacketErrorCount)); - Slog.d(TAG, "RxByteCount = " + String.valueOf(ss.mRxByteCount)); - Slog.d(TAG, "RxPacketCount = " + String.valueOf(ss.mRxPacketCount)); - Slog.d(TAG, "RxPacketErrorCount = " - + String.valueOf(ss.mRxPacketErrorCount)); - Slog.d(TAG, "Timestamp = " + String.valueOf(ss.mTimestamp)); - Slog.d(TAG, "---------------------------"); + try { + SamplingSnapshot ss = new SamplingSnapshot(); + + ss.mTxByteCount = Long.parseLong(tokens[1]); + ss.mTxPacketCount = Long.parseLong(tokens[2]); + ss.mTxPacketErrorCount = Long.parseLong(tokens[3]); + ss.mRxByteCount = Long.parseLong(tokens[9]); + ss.mRxPacketCount = Long.parseLong(tokens[10]); + ss.mRxPacketErrorCount = Long.parseLong(tokens[11]); + + ss.mTimestamp = SystemClock.elapsedRealtime(); + + if (DBG) { + Slog.d(TAG, "Interface = " + currentIface); + Slog.d(TAG, "ByteCount = " + String.valueOf(ss.mTxByteCount)); + Slog.d(TAG, "TxPacketCount = " + String.valueOf(ss.mTxPacketCount)); + Slog.d(TAG, "TxPacketErrorCount = " + + String.valueOf(ss.mTxPacketErrorCount)); + Slog.d(TAG, "RxByteCount = " + String.valueOf(ss.mRxByteCount)); + Slog.d(TAG, "RxPacketCount = " + String.valueOf(ss.mRxPacketCount)); + Slog.d(TAG, "RxPacketErrorCount = " + + String.valueOf(ss.mRxPacketErrorCount)); + Slog.d(TAG, "Timestamp = " + String.valueOf(ss.mTimestamp)); + Slog.d(TAG, "---------------------------"); + } + + mapIfaceToSample.put(currentIface, ss); + + } catch (NumberFormatException e) { + // just ignore this data point } - - mapIfaceToSample.put(currentIface, ss); } } @@ -179,7 +184,7 @@ public class SamplingDataTracker } } - public int getSampledTxByteCount() { + public long getSampledTxByteCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mTxByteCount - mBeginningSample.mTxByteCount; @@ -189,7 +194,7 @@ public class SamplingDataTracker } } - public int getSampledTxPacketCount() { + public long getSampledTxPacketCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mTxPacketCount - mBeginningSample.mTxPacketCount; @@ -199,7 +204,7 @@ public class SamplingDataTracker } } - public int getSampledTxPacketErrorCount() { + public long getSampledTxPacketErrorCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mTxPacketErrorCount - mBeginningSample.mTxPacketErrorCount; @@ -209,7 +214,7 @@ public class SamplingDataTracker } } - public int getSampledRxByteCount() { + public long getSampledRxByteCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mRxByteCount - mBeginningSample.mRxByteCount; @@ -219,7 +224,7 @@ public class SamplingDataTracker } } - public int getSampledRxPacketCount() { + public long getSampledRxPacketCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mRxPacketCount - mBeginningSample.mRxPacketCount; @@ -229,31 +234,31 @@ public class SamplingDataTracker } } - public int getSampledPacketCount() { + public long getSampledPacketCount() { return getSampledPacketCount(mBeginningSample, mEndingSample); } - public int getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) { + public long getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) { if (begin != null && end != null) { - int rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount; - int txPacketCount = end.mTxPacketCount - begin.mTxPacketCount; + long rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount; + long txPacketCount = end.mTxPacketCount - begin.mTxPacketCount; return rxPacketCount + txPacketCount; } else { return LinkInfo.UNKNOWN; } } - public int getSampledPacketErrorCount() { + public long getSampledPacketErrorCount() { if (mBeginningSample != null && mEndingSample != null) { - int rxPacketErrorCount = getSampledRxPacketErrorCount(); - int txPacketErrorCount = getSampledTxPacketErrorCount(); + long rxPacketErrorCount = getSampledRxPacketErrorCount(); + long txPacketErrorCount = getSampledTxPacketErrorCount(); return rxPacketErrorCount + txPacketErrorCount; } else { return LinkInfo.UNKNOWN; } } - public int getSampledRxPacketErrorCount() { + public long getSampledRxPacketErrorCount() { synchronized(mSamplingDataLock) { if (mBeginningSample != null && mEndingSample != null) { return mEndingSample.mRxPacketErrorCount - mBeginningSample.mRxPacketErrorCount; diff --git a/core/java/android/net/WifiLinkInfo.java b/core/java/android/net/WifiLinkInfo.java index f3b0032..a21f1fe 100644 --- a/core/java/android/net/WifiLinkInfo.java +++ b/core/java/android/net/WifiLinkInfo.java @@ -39,8 +39,8 @@ public final class WifiLinkInfo extends LinkInfo public int mRssi = UNKNOWN; /* packet statistics */ - public int mTxGood = UNKNOWN; - public int mTxBad = UNKNOWN; + public long mTxGood = UNKNOWN; + public long mTxBad = UNKNOWN; /** * Implement the Parcelable interface. @@ -51,8 +51,8 @@ public final class WifiLinkInfo extends LinkInfo dest.writeInt(mType); dest.writeInt(mRssi); - dest.writeInt(mTxGood); - dest.writeInt(mTxBad); + dest.writeLong(mTxGood); + dest.writeLong(mTxBad); dest.writeString(mBssid); } @@ -65,8 +65,8 @@ public final class WifiLinkInfo extends LinkInfo li.mType = in.readInt(); li.mRssi = in.readInt(); - li.mTxGood = in.readInt(); - li.mTxBad = in.readInt(); + li.mTxGood = in.readLong(); + li.mTxBad = in.readLong(); li.mBssid = in.readString(); diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index c1d4ae9..d2a9cdc 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -18,8 +18,6 @@ package android.os; import android.util.ArrayMap; -import java.util.HashMap; - /** * Takes care of the grunt work of maintaining a list of remote interfaces, * typically for the use of performing callbacks from a diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl index 7155096..3bfd9a1 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -16,8 +16,10 @@ package android.print; +import android.print.IPrinterDiscoveryObserver; import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; +import android.print.PrinterId; import android.print.PrintJobInfo; import android.print.PrintAttributes; @@ -34,4 +36,12 @@ interface IPrintManager { int appId, int userId); void cancelPrintJob(int printJobId, int appId, int userId); void restartPrintJob(int printJobId, int appId, int userId); + + void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId); + void startPrinterDiscovery(in IPrinterDiscoveryObserver observer, + in List<PrinterId> priorityList, int userId); + void stopPrinterDiscovery(in IPrinterDiscoveryObserver observer, int userId); + void requestPrinterUpdate(in PrinterId printerId, int userId); + void destroyPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, + int userId); } diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl index 5c8a22a..0a77dab 100644 --- a/core/java/android/print/IPrintSpooler.aidl +++ b/core/java/android/print/IPrintSpooler.aidl @@ -18,7 +18,6 @@ package android.print; import android.content.ComponentName; import android.os.ParcelFileDescriptor; -import android.print.PrinterId; import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; import android.print.IPrintSpoolerClient; @@ -47,9 +46,4 @@ oneway interface IPrintSpooler { int sequence); void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); void setClient(IPrintSpoolerClient client); - - // Printer discovery APIs - void onPrintersAdded(in List<PrinterInfo> printers); - void onPrintersRemoved(in List<PrinterId> printerIds); - void onPrintersUpdated(in List<PrinterInfo> printerIds); } diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl index da60120..8b511d6 100644 --- a/core/java/android/print/IPrintSpoolerClient.aidl +++ b/core/java/android/print/IPrintSpoolerClient.aidl @@ -17,7 +17,6 @@ package android.print; import android.content.ComponentName; -import android.print.PrinterId; import android.print.PrintJobInfo; @@ -30,11 +29,4 @@ oneway interface IPrintSpoolerClient { void onPrintJobQueued(in PrintJobInfo printJob); void onAllPrintJobsForServiceHandled(in ComponentName printService); void onAllPrintJobsHandled(); - - // Printer discovery APIs - void createPrinterDiscoverySession(); - void startPrinterDiscovery(in List<PrinterId> priorityList); - void stopPrinterDiscovery(); - void requestPrinterUpdate(in PrinterId printerId); - void destroyPrinterDiscoverySession(); } diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoveryObserver.aidl new file mode 100644 index 0000000..625f383 --- /dev/null +++ b/core/java/android/print/IPrinterDiscoveryObserver.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.print.IPrintClient; +import android.print.PrinterId; +import android.print.PrinterInfo; + +/** + * Interface for observing discovered printers by a discovery session. + * + * @hide + */ +oneway interface IPrinterDiscoveryObserver { + void onPrintersAdded(in List<PrinterInfo> printers); + void onPrintersRemoved(in List<PrinterId> printerIds); + void onPrintersUpdated(in List<PrinterInfo> printerIds); +} diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 531dcb2..d3e35c3 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -204,6 +204,13 @@ public final class PrintManager { return null; } + /** + * @hide + */ + public PrinterDiscoverySession createPrinterDiscoverySession() { + return new PrinterDiscoverySession(mService, mContext, mUserId); + } + private static final class PrintClient extends IPrintClient.Stub { private final WeakReference<PrintManager> mWeakPrintManager; diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java new file mode 100644 index 0000000..8fbdd9c --- /dev/null +++ b/core/java/android/print/PrinterDiscoverySession.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.print; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @hide + */ +public final class PrinterDiscoverySession { + + private static final String LOG_TAG ="PrinterDiscoverySession"; + + private static final int MSG_PRINTERS_ADDED = 1; + private static final int MSG_PRINTERS_REMOVED = 2; + private static final int MSG_PRINTERS_UPDATED = 3; + + private final ArrayMap<PrinterId, PrinterInfo> mPrinters = + new ArrayMap<PrinterId, PrinterInfo>(); + + private final IPrintManager mPrintManager; + + private final int mUserId; + + private final Handler mHandler; + + private IPrinterDiscoveryObserver mObserver; + + private OnPrintersChangeListener mListener; + + private boolean mIsPrinterDiscoveryStarted; + + public static interface OnPrintersChangeListener { + public void onPrintersChanged(); + } + + PrinterDiscoverySession(IPrintManager printManager, Context context, int userId) { + mPrintManager = printManager; + mUserId = userId; + mHandler = new SessionHandler(context.getMainLooper()); + mObserver = new PrinterDiscoveryObserver(this); + try { + mPrintManager.createPrinterDiscoverySession(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error creating printer discovery session", re); + } + } + + public final void startPrinterDisovery(List<PrinterId> priorityList) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed"); + } + if (!mIsPrinterDiscoveryStarted) { + mIsPrinterDiscoveryStarted = true; + try { + mPrintManager.startPrinterDiscovery(mObserver, priorityList, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error starting printer discovery", re); + } + } + } + + public final void stopPrinterDiscovery() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed"); + } + if (mIsPrinterDiscoveryStarted) { + mIsPrinterDiscoveryStarted = false; + try { + mPrintManager.stopPrinterDiscovery(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error stopping printer discovery", re); + } + } + } + + public final void requestPrinterUpdate(PrinterId printerId) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring reqeust printer update - session destroyed"); + } + try { + mPrintManager.requestPrinterUpdate(printerId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error requesting printer update", re); + } + } + + public final void destroy() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring destroy - session destroyed"); + } + destroyNoCheck(); + } + + public final List<PrinterInfo> getPrinters() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring get printers - session destroyed"); + return Collections.emptyList(); + } + return new ArrayList<PrinterInfo>(mPrinters.values()); + } + + public final boolean isDestroyed() { + throwIfNotCalledOnMainThread(); + return isDestroyedNoCheck(); + } + + public final boolean isPrinterDiscoveryStarted() { + throwIfNotCalledOnMainThread(); + return mIsPrinterDiscoveryStarted; + } + + public final void setOnPrintersChangeListener(OnPrintersChangeListener listener) { + throwIfNotCalledOnMainThread(); + mListener = listener; + } + + @Override + protected final void finalize() throws Throwable { + if (!isDestroyedNoCheck()) { + Log.e(LOG_TAG, "Destroying leaked printer discovery session"); + destroyNoCheck(); + } + super.finalize(); + } + + private boolean isDestroyedNoCheck() { + return (mObserver == null); + } + + private void destroyNoCheck() { + stopPrinterDiscovery(); + try { + mPrintManager.destroyPrinterDiscoverySession(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error destroying printer discovery session", re); + } finally { + mObserver = null; + mPrinters.clear(); + } + } + + private void handlePrintersAdded(List<PrinterInfo> printers) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo addedPrinter = printers.get(i); + if (mPrinters.get(addedPrinter.getId()) == null) { + mPrinters.put(addedPrinter.getId(), addedPrinter); + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void handlePrintersRemoved(List<PrinterId> printerIds) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void handlePrintersUpdated(List<PrinterInfo> printers) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int updatedPrinterCount = printers.size(); + for (int i = 0; i < updatedPrinterCount; i++) { + PrinterInfo updatedPrinter = printers.get(i); + PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId()); + if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) { + mPrinters.put(updatedPrinter.getId(), updatedPrinter); + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void notifyOnPrintersChanged() { + if (mListener != null) { + mListener.onPrintersChanged(); + } + } + + private static void throwIfNotCalledOnMainThread() { + if (!Looper.getMainLooper().isCurrentThread()) { + throw new IllegalAccessError("must be called from the main thread"); + } + } + + private final class SessionHandler extends Handler { + + public SessionHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_PRINTERS_ADDED: { + List<PrinterInfo> printers = (List<PrinterInfo>) message.obj; + handlePrintersAdded(printers); + } break; + + case MSG_PRINTERS_REMOVED: { + List<PrinterId> printerIds = (List<PrinterId>) message.obj; + handlePrintersRemoved(printerIds); + } break; + + case MSG_PRINTERS_UPDATED: { + List<PrinterInfo> printers = (List<PrinterInfo>) message.obj; + handlePrintersUpdated(printers); + } break; + } + } + } + + private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub { + + private final WeakReference<PrinterDiscoverySession> mWeakSession; + + public PrinterDiscoveryObserver(PrinterDiscoverySession session) { + mWeakSession = new WeakReference<PrinterDiscoverySession>(session); + } + + @Override + public void onPrintersAdded(List<PrinterInfo> printers) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_ADDED, + printers).sendToTarget(); + } + } + + @Override + public void onPrintersRemoved(List<PrinterId> printerIds) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_REMOVED, + printerIds).sendToTarget(); + } + } + + @Override + public void onPrintersUpdated(List<PrinterInfo> printers) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_UPDATED, + printers).sendToTarget(); + } + } + } +} 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..1d68241 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2931,6 +2931,11 @@ public final class Settings { throws SettingNotFoundException { String v = getStringForUser(cr, name, userHandle); try { + if (LOCATION_MODE.equals(name)) { + // HACK ALERT: temporary hack to work around b/10491283. + // TODO: once b/10491283 fixed, remove this hack + return getLocationModeForUser(cr, userHandle); + } return Integer.parseInt(v); } catch (NumberFormatException e) { throw new SettingNotFoundException(name); @@ -2957,6 +2962,11 @@ public final class Settings { /** @hide */ public static boolean putIntForUser(ContentResolver cr, String name, int value, int userHandle) { + if (LOCATION_MODE.equals(name)) { + // HACK ALERT: temporary hack to work around b/10491283. + // TODO: once b/10491283 fixed, remove this hack + return setLocationModeForUser(cr, value, userHandle); + } return putStringForUser(cr, name, Integer.toString(value), userHandle); } @@ -3265,10 +3275,21 @@ public final class Settings { /** * Comma-separated list of location providers that activities may access. + * + * @deprecated use {@link #LOCATION_MODE} */ + @Deprecated public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; /** + * The degree of location access enabled by the user, for use with {@link + * #putInt(ContentResolver, String, int)} and {@link #getInt(ContentResolver, String)}. Must + * be one of {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, + * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. + */ + public static final String LOCATION_MODE = "location_mode"; + + /** * Location access disabled */ public static final int LOCATION_MODE_OFF = 0; @@ -4328,7 +4349,7 @@ public final class Settings { * @param cr the content resolver to use * @param provider the location provider to query * @return true if the provider is enabled - * @deprecated use {@link #getLocationMode(ContentResolver)} + * @deprecated use {@link #getInt(ContentResolver, String)} and {@link #LOCATION_MODE} */ @Deprecated public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { @@ -4341,7 +4362,8 @@ public final class Settings { * @param provider the location provider to query * @param userId the userId to query * @return true if the provider is enabled - * @deprecated use {@link #getLocationModeForUser(ContentResolver, int)} + * @deprecated use {@link #getIntForUser(ContentResolver, String, int, int)} and + * {@link #LOCATION_MODE} * @hide */ @Deprecated @@ -4356,7 +4378,7 @@ public final class Settings { * @param cr the content resolver to use * @param provider the location provider to enable or disable * @param enabled true if the provider should be enabled - * @deprecated use {@link #setLocationMode(ContentResolver, int)} + * @deprecated use {@link #putInt(ContentResolver, String, int)} and {@link #LOCATION_MODE} */ @Deprecated public static final void setLocationProviderEnabled(ContentResolver cr, @@ -4366,15 +4388,18 @@ public final class Settings { /** * Thread-safe method for enabling or disabling a single location provider. + * * @param cr the content resolver to use * @param provider the location provider to enable or disable * @param enabled true if the provider should be enabled * @param userId the userId for which to enable/disable providers - * @deprecated use {@link #setLocationModeForUser(ContentResolver, int, int)} + * @return true if the value was set, false on database errors + * @deprecated use {@link #putIntForUser(ContentResolver, String, int, int)} and + * {@link #LOCATION_MODE} * @hide */ @Deprecated - public static final void setLocationProviderEnabledForUser(ContentResolver cr, + public static final boolean setLocationProviderEnabledForUser(ContentResolver cr, String provider, boolean enabled, int userId) { synchronized (mLocationSettingsLock) { // to ensure thread safety, we write the provider name with a '+' or '-' @@ -4385,7 +4410,7 @@ public final class Settings { } else { provider = "-" + provider; } - putStringForUser(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider, + return putStringForUser(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider, userId); } } @@ -4395,13 +4420,20 @@ public final class Settings { * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. * + * TODO: remove callers, make private + * * @param cr the content resolver to use * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY} * @param userId the userId for which to change mode + * @return true if the value was set, false on database errors * * @throws IllegalArgumentException if mode is not one of the supported values + * + * @deprecated use {@link #putIntForUser(ContentResolver, String, int, int)} and + * {@link #LOCATION_MODE} */ - public static final void setLocationModeForUser(ContentResolver cr, int mode, int userId) { + @Deprecated + public static final boolean setLocationModeForUser(ContentResolver cr, int mode, int userId) { synchronized (mLocationSettingsLock) { boolean gps = false; boolean network = false; @@ -4421,10 +4453,11 @@ public final class Settings { default: throw new IllegalArgumentException("Invalid location mode: " + mode); } - Settings.Secure.setLocationProviderEnabledForUser( + boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser( cr, LocationManager.GPS_PROVIDER, gps, userId); - Settings.Secure.setLocationProviderEnabledForUser( + boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser( cr, LocationManager.NETWORK_PROVIDER, network, userId); + return gpsSuccess && nlpSuccess; } } @@ -4433,11 +4466,15 @@ public final class Settings { * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. * + * TODO: remove callers, delete + * * @param cr the content resolver to use * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY} * * @throws IllegalArgumentException if mode is not one of the supported values + * @deprecated use {@link #putInt(ContentResolver, String, int)} and {@link #LOCATION_MODE} */ + @Deprecated public static final void setLocationMode(ContentResolver cr, int mode) { setLocationModeForUser(cr, mode, UserHandle.myUserId()); } @@ -4447,10 +4484,16 @@ public final class Settings { * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. * + * TODO: remove callers, make private + * * @param cr the content resolver to use * @param userId the userId for which to read the mode * @return the location mode + * + * @deprecated use {@link #getIntForUser(ContentResolver, String, int, int)} and + * {@link #LOCATION_MODE} */ + @Deprecated public static final int getLocationModeForUser(ContentResolver cr, int userId) { synchronized (mLocationSettingsLock) { boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser( @@ -4474,9 +4517,14 @@ public final class Settings { * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. * + * TODO: remove callers, delete + * * @param cr the content resolver to use * @return the location mode + * + * @deprecated use {@link #getInt(ContentResolver, String, int)} and {@link #LOCATION_MODE} */ + @Deprecated public static final int getLocationMode(ContentResolver cr) { return getLocationModeForUser(cr, UserHandle.myUserId()); } @@ -5075,6 +5123,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/android/speech/hotword/HotwordRecognitionListener.java b/core/java/android/speech/hotword/HotwordRecognitionListener.java new file mode 100644 index 0000000..8e62373 --- /dev/null +++ b/core/java/android/speech/hotword/HotwordRecognitionListener.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.speech.hotword; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; + +/** + * Used for receiving notifications from the HotwordRecognitionService when the + * hotword recognition related events occur. + * All the callbacks are executed on the application main thread. + * {@hide} + */ +public interface HotwordRecognitionListener { + /** + * Called when the service starts listening for hotword. + */ + void onHotwordRecognitionStarted(); + + /** + * Called when the service stops listening for hotword. + */ + void onHotwordRecognitionStopped(); + + /** + * Called on an event of interest to the client. + * + * @param eventType the event type. + * @param eventBundle a Bundle containing the hotword event(s). + */ + void onHotwordEvent(int eventType, Bundle eventBundle); + + /** + * Called back when hotword is detected. + * The action tells the client what action to take, post hotword-detection. + */ + void onHotwordRecognized(PendingIntent intent); + + /** + * Called when the HotwordRecognitionService encounters an error. + * + * @param errorCode the error code describing the error that was encountered. + */ + void onHotwordError(int errorCode); +}
\ No newline at end of file diff --git a/core/java/android/speech/hotword/HotwordRecognitionService.java b/core/java/android/speech/hotword/HotwordRecognitionService.java new file mode 100644 index 0000000..c16d2a9 --- /dev/null +++ b/core/java/android/speech/hotword/HotwordRecognitionService.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.speech.hotword; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +/** + * This class provides a base class for hotword detection service implementations. + * This class should be extended only if you wish to implement a new hotword recognizer. + * {@hide} + */ +public abstract class HotwordRecognitionService extends Service { + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.speech.hotword.HotwordRecognitionService"; + + /** Log messages identifier */ + private static final String TAG = "HotwordRecognitionService"; + + /** Debugging flag */ + // TODO: Turn off. + private static final boolean DBG = true; + + private static final int MSG_START_RECOGNITION = 1; + private static final int MSG_STOP_RECOGNITION = 2; + + /** + * The current callback of an application that invoked the + * {@link HotwordRecognitionService#onStartHotwordRecognition(Callback)} method + */ + private Callback mCurrentCallback = null; + + // Handle the client dying. + private final IBinder.DeathRecipient mCallbackDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + if (DBG) Log.i(TAG, "HotwordRecognitionService listener died"); + mCurrentCallback = null; + } + }; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_RECOGNITION: + dispatchStartRecognition((IHotwordRecognitionListener) msg.obj); + break; + case MSG_STOP_RECOGNITION: + dispatchStopRecognition((IHotwordRecognitionListener) msg.obj); + break; + } + } + }; + + /** Binder of the hotword recognition service */ + private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this); + + private void dispatchStartRecognition(IHotwordRecognitionListener listener) { + if (mCurrentCallback == null) { + if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder()); + try { + listener.asBinder().linkToDeath(mCallbackDeathRecipient, 0); + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "listener died before linkToDeath()"); + } + mCurrentCallback = new Callback(listener); + HotwordRecognitionService.this.onStartHotwordRecognition(mCurrentCallback); + } else { + try { + listener.onHotwordError(HotwordRecognizer.ERROR_RECOGNIZER_BUSY); + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "onError call from startRecognition failed"); + } + if (DBG) Log.d(TAG, "concurrent startRecognition received - ignoring this call"); + } + } + + private void dispatchStopRecognition(IHotwordRecognitionListener listener) { + try { + if (mCurrentCallback == null) { + listener.onHotwordError(HotwordRecognizer.ERROR_CLIENT); + Log.w(TAG, "stopRecognition called with no preceding startRecognition - ignoring"); + } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) { + listener.onHotwordError(HotwordRecognizer.ERROR_RECOGNIZER_BUSY); + Log.w(TAG, "stopRecognition called by a different caller - ignoring"); + } else { // the correct state + HotwordRecognitionService.this.onStopHotwordRecognition(mCurrentCallback); + mCurrentCallback = null; + } + } catch (RemoteException e) { // occurs if onError fails + if (DBG) Log.d(TAG, "onError call from stopRecognition failed"); + } + } + + @Override + public IBinder onBind(final Intent intent) { + if (DBG) Log.d(TAG, "onBind, intent=" + intent); + return mBinder; + } + + @Override + public void onDestroy() { + if (DBG) Log.d(TAG, "onDestroy"); + if (mCurrentCallback != null) { + mCurrentCallback.mListener.asBinder().unlinkToDeath(mCallbackDeathRecipient, 0); + mCurrentCallback = null; + } + mBinder.clearReference(); + super.onDestroy(); + } + + /** + * Checks whether the caller has sufficient permissions + * + * @param listener to send the error message to in case of error + * @return {@code true} if the caller has enough permissions, {@code false} otherwise + */ + private boolean checkPermissions(IHotwordRecognitionListener listener) { + if (DBG) Log.d(TAG, "checkPermissions"); + if (checkCallingOrSelfPermission( + android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { + return true; + } + try { + Log.e(TAG, "Recognition service called without RECORD_AUDIO permissions"); + listener.onHotwordError(HotwordRecognizer.ERROR_FAILED); + } catch (RemoteException e) { + Log.e(TAG, "onHotwordError(ERROR_INSUFFICIENT_PERMISSIONS) message failed", e); + } + return false; + } + + /** + * Notifies the service to start a recognition. + * + * @param callback that receives the callbacks from the service. + */ + public abstract void onStartHotwordRecognition(Callback callback); + + /** + * Notifies the service to stop recognition. + * + * @param callback that receives the callbacks from the service. + */ + public abstract void onStopHotwordRecognition(Callback callback); + + /** Binder of the hotword recognition service */ + private static class RecognitionServiceBinder extends IHotwordRecognitionService.Stub { + private HotwordRecognitionService mInternalService; + + public RecognitionServiceBinder(HotwordRecognitionService service) { + mInternalService = service; + } + + public void startHotwordRecognition(IHotwordRecognitionListener listener) { + if (DBG) Log.d(TAG, "startRecognition called by: " + listener.asBinder()); + if (mInternalService != null && mInternalService.checkPermissions(listener)) { + mInternalService.mHandler.sendMessage( + Message.obtain(mInternalService.mHandler, MSG_START_RECOGNITION, listener)); + } + } + + public void stopHotwordRecognition(IHotwordRecognitionListener listener) { + if (DBG) Log.d(TAG, "stopRecognition called by: " + listener.asBinder()); + if (mInternalService != null) { + mInternalService.mHandler.sendMessage( + Message.obtain(mInternalService.mHandler, MSG_STOP_RECOGNITION, listener)); + } + } + + private void clearReference() { + mInternalService = null; + } + } + + /** + * This class acts passes on the callbacks received from the Hotword service + * to the listener. + */ + public static class Callback { + private final IHotwordRecognitionListener mListener; + + private Callback(IHotwordRecognitionListener listener) { + mListener = listener; + } + + /** + * Called when the service starts listening for hotword. + */ + public void onHotwordRecognitionStarted() throws RemoteException { + mListener.onHotwordRecognitionStarted(); + } + + /** + * Called when the service starts listening for hotword. + */ + public void onHotwordRecognitionStopped() throws RemoteException { + mListener.onHotwordRecognitionStopped(); + } + + /** + * Called on an event of interest to the client. + * + * @param eventType the event type. Event types are defined in {@link HotwordRecognizer}. + * @param eventBundle a Bundle containing the hotword event(s). + */ + public void onHotwordEvent(int eventType, Bundle eventBundle) throws RemoteException { + mListener.onHotwordEvent(eventType, eventBundle); + } + + /** + * Called back when hotword is detected. + * The action tells the client what action to take, post hotword-detection. + */ + public void onHotwordRecognized(PendingIntent intent) throws RemoteException { + mListener.onHotwordRecognized(intent); + } + + /** + * Called when the HotwordRecognitionService encounters an error. + * + * @param errorCode the error code describing the error that was encountered. + * Error codes are defined in {@link HotwordRecognizer}. + */ + public void onError(int errorCode) throws RemoteException { + mListener.onHotwordError(errorCode); + } + } +} diff --git a/core/java/android/speech/hotword/HotwordRecognizer.java b/core/java/android/speech/hotword/HotwordRecognizer.java new file mode 100644 index 0000000..c6bd1f3 --- /dev/null +++ b/core/java/android/speech/hotword/HotwordRecognizer.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.speech.hotword; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * This class provides access to the Hotword recognition service. + * This class's methods must be invoked on the main application thread. + * {@hide} + */ +public class HotwordRecognizer { + /** DEBUG value to enable verbose debug prints */ + // TODO: Turn off. + private final static boolean DBG = true; + + /** Log messages identifier */ + private static final String TAG = "HotwordRecognizer"; + + /** + * Key used to retrieve a string to be displayed to the user passed to the + * {@link android.speech.hotword.HotwordRecognitionListener#onHotwordEvent(int, Bundle)} method. + */ + public static final String PROMPT_TEXT = "prompt_text"; + + /** + * Event type used to indicate to the user that the hotword service has changed + * its state. + */ + public static final int EVENT_TYPE_STATE_CHANGED = 1; + + /** Audio recording error. */ + public static final int ERROR_AUDIO = 1; + + /** RecognitionService busy. */ + public static final int ERROR_RECOGNIZER_BUSY = 2; + + /** This indicates a permanent failure and the clients shouldn't retry on this */ + public static final int ERROR_FAILED = 3; + + /** Client-side errors */ + public static final int ERROR_CLIENT = 4; + + /** The service timed out */ + public static final int ERROR_TIMEOUT = 5; + + /** The service received concurrent start calls */ + public static final int ERROR_SERVICE_ALREADY_STARTED = 6; + + /** action codes */ + private static final int MSG_START = 1; + private static final int MSG_STOP = 2; + private final static int MSG_CHANGE_LISTENER = 3; + + /** The underlying HotwordRecognitionService endpoint */ + private IHotwordRecognitionService mService; + + /** The connection to the actual service */ + private Connection mConnection; + + /** Context with which the manager was created */ + private final Context mContext; + + /** Component to direct service intent to */ + private final ComponentName mServiceComponent; + + /** Handler that will execute the main tasks */ + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START: + handleStartRecognition(); + break; + case MSG_STOP: + handleStopRecognition(); + break; + case MSG_CHANGE_LISTENER: + handleChangeListener((HotwordRecognitionListener) msg.obj); + break; + } + } + }; + + /** + * Temporary queue, saving the messages until the connection will be established, afterwards, + * only mHandler will receive the messages + */ + private final Queue<Message> mPendingTasks = new LinkedList<Message>(); + + /** The Listener that will receive all the callbacks */ + private final InternalListener mListener = new InternalListener(); + + /** + * Checks whether a hotword recognition service is available on the system. If this method + * returns {@code false}, {@link HotwordRecognizer#createHotwordRecognizer(Context)} will + * fail. + * + * @param context with which {@code HotwordRecognizer} will be created + * @return {@code true} if recognition is available, {@code false} otherwise + */ + public static boolean isHotwordRecognitionAvailable(final Context context) { + final List<ResolveInfo> list = context.getPackageManager().queryIntentServices( + new Intent(HotwordRecognitionService.SERVICE_INTERFACE), 0); + return list != null && list.size() != 0; + } + + /** + * Factory method to create a new {@code HotwordRecognizer}. Please note that + * {@link #setRecognitionListener(HotwordRecognitionListener)} + * should be called before dispatching any command to the created {@code HotwordRecognizer}, + * otherwise no notifications will be received. + * + * @param context in which to create {@code HotwordRecognizer} + * @return a new {@code HotwordRecognizer} + */ + public static HotwordRecognizer createHotwordRecognizer(final Context context) { + return createHotwordRecognizer(context, null); + } + + + /** + * Factory method to create a new {@code HotwordRecognizer}. Please note that + * {@link #setRecognitionListener(HotwordRecognitionListener)} + * should be called before dispatching any command to the created {@code HotwordRecognizer}, + * otherwise no notifications will be received. + * + * Use this version of the method to specify a specific service to direct this + * {@link HotwordRecognizer} to. Normally you would not use this; use + * {@link #createHotwordRecognizer(Context)} instead to use the system default recognition + * service. + * + * @param context in which to create {@code HotwordRecognizer} + * @param serviceComponent the {@link ComponentName} of a specific service to direct this + * {@code HotwordRecognizer} to + * @return a new {@code HotwordRecognizer} + */ + public static HotwordRecognizer createHotwordRecognizer( + final Context context, final ComponentName serviceComponent) { + if (context == null) { + throw new IllegalArgumentException("Context cannot be null)"); + } + checkIsCalledFromMainThread(); + return new HotwordRecognizer(context, serviceComponent); + } + + /** + * Sets the listener that will receive all the callbacks. The previous unfinished commands will + * be executed with the old listener, while any following command will be executed with the new + * listener. + * + * @param listener listener that will receive all the callbacks from the created + * {@link HotwordRecognizer}, this must not be null. + */ + public void setRecognitionListener(HotwordRecognitionListener listener) { + checkIsCalledFromMainThread(); + putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + } + + /** + * Starts recognizing hotword. Please note that + * {@link #setRecognitionListener(HotwordRecognitionListener)} should be called beforehand, + * otherwise no notifications will be received. + */ + public void startRecognition() { + checkIsCalledFromMainThread(); + if (mConnection == null) { // first time connection + mConnection = new Connection(); + + Intent serviceIntent = new Intent(HotwordRecognitionService.SERVICE_INTERFACE); + + + if (mServiceComponent == null) { + // TODO: Resolve the ComponentName here and use it. + String serviceComponent = null; + if (TextUtils.isEmpty(serviceComponent)) { + Log.e(TAG, "no selected voice recognition service"); + mListener.onHotwordError(ERROR_CLIENT); + return; + } + serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent)); + } else { + serviceIntent.setComponent(mServiceComponent); + } + + if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) { + Log.e(TAG, "bind to recognition service failed"); + mConnection = null; + mService = null; + mListener.onHotwordError(ERROR_CLIENT); + return; + } + } else { + mListener.onHotwordError(ERROR_SERVICE_ALREADY_STARTED); + return; + } + putMessage(Message.obtain(mHandler, MSG_START)); + } + + /** + * Stops recognizing hotword. Please note that + * {@link #setRecognitionListener(HotwordRecognitionListener)} should be called beforehand, + * otherwise no notifications will be received. + */ + public void stopRecognition() { + checkIsCalledFromMainThread(); + putMessage(Message.obtain(mHandler, MSG_STOP)); + } + + // Private constructor. + private HotwordRecognizer(Context context, ComponentName serviceComponent) { + mContext = context; + mServiceComponent = serviceComponent; + } + + /** + * Destroys the {@code HotwordRecognizer} object. + */ + public void destroy() { + if (mConnection != null) { + mContext.unbindService(mConnection); + } + mPendingTasks.clear(); + mService = null; + mConnection = null; + mListener.mInternalListener = null; + } + + private void handleStartRecognition() { + if (!checkOpenConnection()) { + return; + } + try { + mService.startHotwordRecognition(mListener); + if (DBG) Log.d(TAG, "service startRecognition command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "startRecognition() failed", e); + mListener.onHotwordError(ERROR_CLIENT); + } + } + + + private void handleStopRecognition() { + if (!checkOpenConnection()) { + return; + } + try { + mService.stopHotwordRecognition(mListener); + if (DBG) Log.d(TAG, "service stopRecognition command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "stopRecognition() failed", e); + mListener.onHotwordError(ERROR_CLIENT); + } + } + + /** changes the listener */ + private void handleChangeListener(HotwordRecognitionListener listener) { + if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener); + mListener.mInternalListener = listener; + } + + private boolean checkOpenConnection() { + if (mService != null) { + return true; + } + mListener.onHotwordError(ERROR_CLIENT); + Log.e(TAG, "not connected to the recognition service"); + return false; + } + + private static void checkIsCalledFromMainThread() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException( + "HotwordRecognizer should be used only from the application's main thread"); + } + } + + private void putMessage(Message msg) { + if (mService == null) { + mPendingTasks.offer(msg); + } else { + mHandler.sendMessage(msg); + } + } + + /** + * Basic ServiceConnection that records the mService variable. + * Additionally, on creation it invokes + * {@link IHotwordRecognitionService#startHotwordRecognition(IHotwordRecognitionListener)}. + */ + private class Connection implements ServiceConnection { + + public void onServiceConnected(final ComponentName name, final IBinder service) { + // always done on the application main thread, so no need to send message to mHandler + mService = IHotwordRecognitionService.Stub.asInterface(service); + if (DBG) Log.d(TAG, "onServiceConnected - Success"); + while (!mPendingTasks.isEmpty()) { + mHandler.sendMessage(mPendingTasks.poll()); + } + } + + public void onServiceDisconnected(final ComponentName name) { + // always done on the application main thread, so no need to send message to mHandler + mService = null; + mConnection = null; + mPendingTasks.clear(); + if (DBG) Log.d(TAG, "onServiceDisconnected - Success"); + } + } + + /** + * Internal wrapper of IHotwordRecognitionListener which will propagate the results to + * HotwordRecognitionListener. + */ + private class InternalListener extends IHotwordRecognitionListener.Stub { + private HotwordRecognitionListener mInternalListener; + + private final static int MSG_ON_START = 1; + private final static int MSG_ON_STOP = 2; + private final static int MSG_ON_EVENT = 3; + private final static int MSG_ON_RECOGNIZED = 4; + private final static int MSG_ON_ERROR = 5; + + private final Handler mInternalHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (mInternalListener == null) { + return; + } + switch (msg.what) { + case MSG_ON_START: + mInternalListener.onHotwordRecognitionStarted(); + break; + case MSG_ON_STOP: + mInternalListener.onHotwordRecognitionStopped(); + break; + case MSG_ON_EVENT: + mInternalListener.onHotwordEvent(msg.arg1, (Bundle) msg.obj); + break; + case MSG_ON_RECOGNIZED: + mInternalListener.onHotwordRecognized((PendingIntent) msg.obj); + break; + case MSG_ON_ERROR: + mInternalListener.onHotwordError((Integer) msg.obj); + break; + } + } + }; + + @Override + public void onHotwordRecognitionStarted() throws RemoteException { + Message.obtain(mInternalHandler, MSG_ON_START).sendToTarget(); + } + + @Override + public void onHotwordRecognitionStopped() throws RemoteException { + Message.obtain(mInternalHandler, MSG_ON_STOP).sendToTarget(); + } + + @Override + public void onHotwordEvent(final int eventType, final Bundle params) { + Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params) + .sendToTarget(); + } + + @Override + public void onHotwordRecognized(PendingIntent intent) throws RemoteException { + Message.obtain(mInternalHandler, MSG_ON_RECOGNIZED, intent) + .sendToTarget(); + } + + @Override + public void onHotwordError(final int error) { + Message.obtain(mInternalHandler, MSG_ON_ERROR, error).sendToTarget(); + } + } +} diff --git a/core/java/android/speech/hotword/IHotwordRecognitionListener.aidl b/core/java/android/speech/hotword/IHotwordRecognitionListener.aidl new file mode 100644 index 0000000..49c5233 --- /dev/null +++ b/core/java/android/speech/hotword/IHotwordRecognitionListener.aidl @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech.hotword; + +import android.app.PendingIntent; +import android.os.Bundle; + +/** + * Listener for hotword detection events. + * This indicates when the hotword was detected, and also notifies the + * client of the intermediate events that may be used to show visual feedback + * to the user. + * {@hide} + */ +oneway interface IHotwordRecognitionListener { + /** + * Called when the service starts listening for hotword. + */ + void onHotwordRecognitionStarted(); + + /** + * Called when the service starts listening for hotword. + */ + void onHotwordRecognitionStopped(); + + /** + * Called on an event of interest to the client. + * + * @param eventType the event type. + * @param eventBundle a Bundle containing the hotword event(s). + */ + void onHotwordEvent(in int eventType, in Bundle eventBundle); + + /** + * Called back when hotword is detected. + * The action tells the client what action to take, post hotword-detection. + */ + void onHotwordRecognized(in PendingIntent intent); + + /** + * Called when the HotwordRecognitionService encounters an error. + * + * @param errorCode the error code describing the error that was encountered. + */ + void onHotwordError(in int errorCode); +} diff --git a/core/java/android/speech/hotword/IHotwordRecognitionService.aidl b/core/java/android/speech/hotword/IHotwordRecognitionService.aidl new file mode 100644 index 0000000..331d81c --- /dev/null +++ b/core/java/android/speech/hotword/IHotwordRecognitionService.aidl @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech.hotword; + +import android.speech.hotword.IHotwordRecognitionListener; + +/** + * A service interface to Hotword recognition. + * Call startHotwordDetection with a listener when you want to begin detecting + * hotword; + * The service would automatically stop detection when hotword is detected; + * So it's a create-once use-once service. + * The service doesn't support nested calls to start detection and disallows them. + * {@hide} + */ +oneway interface IHotwordRecognitionService { + /** + * Start hotword recognition. + * The clients should rely on the callback to figure out if the detection was + * started. + * + * @param listener a listener to notify of hotword events. + */ + void startHotwordRecognition(in IHotwordRecognitionListener listener); + + /** + * Stop hotword recognition. + * Stops the recognition only if it was started by the same caller. + * + * @param listener a listener to notify of hotword events. + */ + void stopHotwordRecognition(in IHotwordRecognitionListener listener); +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5269ee3..4a3803e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6680,8 +6680,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Returns whether this View is accessibility focused. * * @return True if this View is accessibility focused. + * @hide */ - boolean isAccessibilityFocused() { + public boolean isAccessibilityFocused() { return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0; } @@ -7389,7 +7390,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void dispatchStartTemporaryDetach() { - clearAccessibilityFocus(); clearDisplayList(); onStartTemporaryDetach(); @@ -11860,7 +11860,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, jumpDrawablesToCurrentState(); - clearAccessibilityFocus(); resetSubtreeAccessibilityStateChanged(); if (isFocused()) { diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 8ae6996..26596d9 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -269,6 +269,23 @@ public interface ViewParent { /** * Called when a child view now has or no longer is tracking transient state. * + * <p>"Transient state" is any state that a View might hold that is not expected to + * be reflected in the data model that the View currently presents. This state only + * affects the presentation to the user within the View itself, such as the current + * state of animations in progress or the state of a text selection operation.</p> + * + * <p>Transient state is useful for hinting to other components of the View system + * that a particular view is tracking something complex but encapsulated. + * A <code>ListView</code> for example may acknowledge that list item Views + * with transient state should be preserved within their position or stable item ID + * instead of treating that view as trivially replaceable by the backing adapter. + * This allows adapter implementations to be simpler instead of needing to track + * the state of item view animations in progress such that they could be restored + * in the event of an unexpected recycling and rebinding of attached item views.</p> + * + * <p>This method is called on a parent view when a child view or a view within + * its subtree begins or ends tracking of internal transient state.</p> + * * @param child Child view whose state has changed * @param hasTransientState true if this child has transient state */ diff --git a/core/java/android/view/transition/Visibility.java b/core/java/android/view/transition/Visibility.java index 96ea044..4df53da 100644 --- a/core/java/android/view/transition/Visibility.java +++ b/core/java/android/view/transition/Visibility.java @@ -29,11 +29,25 @@ import android.view.ViewParent; * views exist in the current view hierarchy. The class is intended to be a * utility for subclasses such as {@link Fade}, which use this visibility * information to determine the specific animations to run when visibility - * changes occur. Subclasses should implement one or more of the methods - * {@link #appear(ViewGroup, TransitionValues, int, TransitionValues, int)}, - * {@link #disappear(ViewGroup, TransitionValues, int, TransitionValues, int)}, - * {@link #appear(ViewGroup, TransitionValues, int, TransitionValues, int)}, and + * changes occur. Subclasses should implement one or both of the methods + * {@link #appear(ViewGroup, TransitionValues, int, TransitionValues, int), and * {@link #disappear(ViewGroup, TransitionValues, int, TransitionValues, int)}. + * + * <p>Note that a view's visibility change is determined by both whether the view + * itself is changing and whether its parent hierarchy's visibility is changing. + * That is, a view that appears in the end scene will only trigger a call to + * {@link #appear(android.view.ViewGroup, TransitionValues, int, TransitionValues, int) + * appear()} if its parent hierarchy was stable between the start and end scenes. + * This is done to avoid causing a visibility transition on every node in a hierarchy + * when only the top-most node is the one that should be transitioned in/out. + * Stability is determined by either the parent hierarchy views being the same + * between scenes or, if scenes are inflated from layout resource files and thus + * have result in different view instances, if the views represented by + * the ids of those parents are stable. This means that visibility determination + * is more effective with inflated view hierarchies if ids are used. + * The exception to this is when the visibility subclass transition is + * targeted at specific views, in which case the visibility of parent views + * is ignored.</p> */ public abstract class Visibility extends Transition { @@ -49,8 +63,8 @@ public abstract class Visibility extends Transition { boolean fadeIn; int startVisibility; int endVisibility; - View startParent; - View endParent; + ViewGroup startParent; + ViewGroup endParent; } // Temporary structure, used in calculating state in setup() and play() @@ -93,28 +107,47 @@ public abstract class Visibility extends Transition { return visibility == View.VISIBLE && parent != null; } - private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup view) { + /** + * Tests whether the hierarchy, up to the scene root, changes visibility between + * start and end scenes. This is done to ensure that a view that changes visibility + * is only animated if that view's parent was stable between scenes; we should not + * fade an entire hierarchy, but rather just the top-most node in the hierarchy that + * changed visibility. Note that both the start and end parents are passed in + * because the instances may differ for the same view due to layout inflation + * between scenes. + * + * @param sceneRoot The root of the scene hierarchy + * @param startView The container view in the start scene + * @param endView The container view in the end scene + * @return true if the parent hierarchy experienced a visibility change, false + * otherwise + */ + private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup startView, + ViewGroup endView) { - if (view == sceneRoot) { + if (startView == sceneRoot || endView == sceneRoot) { return false; } - TransitionValues startValues = getTransitionValues(view, true); - TransitionValues endValues = getTransitionValues(view, false); + TransitionValues startValues = startView != null ? + getTransitionValues(startView, true) : getTransitionValues(endView, true); + TransitionValues endValues = endView != null ? + getTransitionValues(endView, false) : getTransitionValues(startView, false); if (startValues == null || endValues == null) { return true; } - int startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); - View startParent = (View) startValues.values.get(PROPNAME_PARENT); - int endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); - View endParent = (View) endValues.values.get(PROPNAME_PARENT); + Integer visibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); + int startVisibility = (visibility != null) ? visibility : -1; + ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); + visibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); + int endVisibility = (visibility != null) ? visibility : -1; + ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); if (startVisibility != endVisibility || startParent != endParent) { return true; } - ViewParent parent = view.getParent(); - if (parent instanceof ViewGroup && parent != sceneRoot) { - return isHierarchyVisibilityChanging(sceneRoot, (ViewGroup) parent); + if (startParent != null || endParent != null) { + return isHierarchyVisibilityChanging(sceneRoot, startParent, endParent); } return false; } @@ -126,14 +159,14 @@ public abstract class Visibility extends Transition { visInfo.fadeIn = false; if (startValues != null) { visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY); - visInfo.startParent = (View) startValues.values.get(PROPNAME_PARENT); + visInfo.startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT); } else { visInfo.startVisibility = -1; visInfo.startParent = null; } if (endValues != null) { visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY); - visInfo.endParent = (View) endValues.values.get(PROPNAME_PARENT); + visInfo.endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT); } else { visInfo.endVisibility = -1; visInfo.endParent = null; @@ -177,20 +210,27 @@ public abstract class Visibility extends Transition { protected Animator play(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues); - // Ensure not in parent hierarchy that's also becoming visible/invisible if (visInfo.visibilityChange) { - ViewGroup parent = (ViewGroup) ((visInfo.endParent != null) ? - visInfo.endParent : visInfo.startParent); - if (parent != null) { - if (!isHierarchyVisibilityChanging(sceneRoot, parent)) { - if (visInfo.fadeIn) { - return appear(sceneRoot, startValues, visInfo.startVisibility, - endValues, visInfo.endVisibility); - } else { - return disappear(sceneRoot, startValues, visInfo.startVisibility, - endValues, visInfo.endVisibility - ); - } + // Only transition views that are either targets of this transition + // or whose parent hierarchies remain stable between scenes + boolean isTarget = false; + if (mTargets != null || mTargetIds != null) { + View startView = startValues != null ? startValues.view : null; + View endView = endValues != null ? endValues.view : null; + int startId = startView != null ? startView.getId() : -1; + int endId = endView != null ? endView.getId() : -1; + isTarget = isValidTarget(startView, startId) || isValidTarget(endView, endId); + } + if (isTarget || ((visInfo.startParent != null || visInfo.endParent != null) && + !isHierarchyVisibilityChanging(sceneRoot, + visInfo.startParent, visInfo.endParent))) { + if (visInfo.fadeIn) { + return appear(sceneRoot, startValues, visInfo.startVisibility, + endValues, visInfo.endVisibility); + } else { + return disappear(sceneRoot, startValues, visInfo.startVisibility, + endValues, visInfo.endVisibility + ); } } } diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 8919248..6262387 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -32,12 +32,15 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; +import android.view.View.OnAttachStateChangeListener; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AccelerateDecelerateInterpolator; +import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller; + import java.util.Locale; /** @@ -1133,18 +1136,32 @@ public class ListPopupWindow { * * @hide */ - public static abstract class ForwardingListener implements View.OnTouchListener { + public static abstract class ForwardingListener + implements View.OnTouchListener, View.OnAttachStateChangeListener { /** Scaled touch slop, used for detecting movement outside bounds. */ private final float mScaledTouchSlop; + /** Timeout before disallowing intercept on the source's parent. */ + private final int mTapTimeout; + + /** Source view from which events are forwarded. */ + private final View mSrc; + + /** Runnable used to prevent conflicts with scrolling parents. */ + private Runnable mDisallowIntercept; + /** Whether this listener is currently forwarding touch events. */ private boolean mForwarding; /** The id of the first pointer down in the current event stream. */ private int mActivePointerId; - public ForwardingListener(Context context) { - mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + public ForwardingListener(View src) { + mSrc = src; + mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop(); + mTapTimeout = ViewConfiguration.getTapTimeout(); + + src.addOnAttachStateChangeListener(this); } /** @@ -1164,15 +1181,29 @@ public class ListPopupWindow { final boolean wasForwarding = mForwarding; final boolean forwarding; if (wasForwarding) { - forwarding = onTouchForwarded(v, event) || !onForwardingStopped(); + forwarding = onTouchForwarded(event) || !onForwardingStopped(); } else { - forwarding = onTouchObserved(v, event) && onForwardingStarted(); + forwarding = onTouchObserved(event) && onForwardingStarted(); } mForwarding = forwarding; return forwarding || wasForwarding; } + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + mForwarding = false; + mActivePointerId = MotionEvent.INVALID_POINTER_ID; + + if (mDisallowIntercept != null) { + mSrc.removeCallbacks(mDisallowIntercept); + } + } + /** * Called when forwarding would like to start. * <p> @@ -1182,7 +1213,7 @@ public class ListPopupWindow { * * @return true to start forwarding, false otherwise */ - public boolean onForwardingStarted() { + protected boolean onForwardingStarted() { final ListPopupWindow popup = getPopup(); if (popup != null && !popup.isShowing()) { popup.show(); @@ -1199,7 +1230,7 @@ public class ListPopupWindow { * * @return true to stop forwarding, false otherwise */ - public boolean onForwardingStopped() { + protected boolean onForwardingStopped() { final ListPopupWindow popup = getPopup(); if (popup != null && popup.isShowing()) { popup.dismiss(); @@ -1210,29 +1241,45 @@ public class ListPopupWindow { /** * Observes motion events and determines when to start forwarding. * - * @param src view from which the event originated * @param srcEvent motion event in source view coordinates * @return true to start forwarding motion events, false otherwise */ - private boolean onTouchObserved(View src, MotionEvent srcEvent) { + private boolean onTouchObserved(MotionEvent srcEvent) { + final View src = mSrc; if (!src.isEnabled()) { return false; } - // The first pointer down is always the active pointer. final int actionMasked = srcEvent.getActionMasked(); - if (actionMasked == MotionEvent.ACTION_DOWN) { - mActivePointerId = srcEvent.getPointerId(0); - } - - final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); - if (activePointerIndex >= 0) { - final float x = srcEvent.getX(activePointerIndex); - final float y = srcEvent.getY(activePointerIndex); - if (!src.pointInView(x, y, mScaledTouchSlop)) { - // The pointer has moved outside of the view. - return true; - } + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = srcEvent.getPointerId(0); + if (mDisallowIntercept == null) { + mDisallowIntercept = new DisallowIntercept(); + } + src.postDelayed(mDisallowIntercept, mTapTimeout); + break; + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId); + if (activePointerIndex >= 0) { + final float x = srcEvent.getX(activePointerIndex); + final float y = srcEvent.getY(activePointerIndex); + if (!src.pointInView(x, y, mScaledTouchSlop)) { + // The pointer has moved outside of the view. + if (mDisallowIntercept != null) { + src.removeCallbacks(mDisallowIntercept); + } + src.getParent().requestDisallowInterceptTouchEvent(true); + return true; + } + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mDisallowIntercept != null) { + src.removeCallbacks(mDisallowIntercept); + } + break; } return false; @@ -1242,11 +1289,11 @@ public class ListPopupWindow { * Handled forwarded motion events and determines when to stop * forwarding. * - * @param src view from which the event originated * @param srcEvent motion event in source view coordinates * @return true to continue forwarding motion events, false to cancel */ - private boolean onTouchForwarded(View src, MotionEvent srcEvent) { + private boolean onTouchForwarded(MotionEvent srcEvent) { + final View src = mSrc; final ListPopupWindow popup = getPopup(); if (popup == null || !popup.isShowing()) { return false; @@ -1267,6 +1314,14 @@ public class ListPopupWindow { dstEvent.recycle(); return handled; } + + private class DisallowIntercept implements Runnable { + @Override + public void run() { + final ViewParent parent = mSrc.getParent(); + parent.requestDisallowInterceptTouchEvent(true); + } + } } /** @@ -1276,8 +1331,6 @@ public class ListPopupWindow { * passed to the drop down in this mode; the list only looks focused.</p> */ private static class DropDownListView extends ListView { - private static final String TAG = ListPopupWindow.TAG + ".DropDownListView"; - /** Duration in milliseconds of the drag-to-open click animation. */ private static final long CLICK_ANIM_DURATION = 150; @@ -1339,6 +1392,9 @@ public class ListPopupWindow { /** Current drag-to-open click animation, if any. */ private Animator mClickAnimation; + /** Helper for drag-to-open auto scrolling. */ + private AbsListViewAutoScroller mScrollHelper; + /** * <p>Creates a new list view wrapper.</p> * @@ -1399,6 +1455,17 @@ public class ListPopupWindow { clearPressedItem(); } + // Manage automatic scrolling. + if (handledEvent) { + if (mScrollHelper == null) { + mScrollHelper = new AbsListViewAutoScroller(this); + } + mScrollHelper.setEnabled(true); + mScrollHelper.onTouch(this, event); + } else if (mScrollHelper != null) { + mScrollHelper.setEnabled(false); + } + return handledEvent; } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 2f42ae3..014306d 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -42,7 +42,6 @@ import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityNodeProvider; import android.widget.RemoteViews.RemoteView; import java.util.ArrayList; @@ -1507,10 +1506,6 @@ public class ListView extends AbsListView { View oldFirst = null; View newSel = null; - AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null; - View accessibilityFocusLayoutRestoreView = null; - int accessibilityFocusPosition = INVALID_POSITION; - // Remember stuff we will need down below switch (mLayoutMode) { case LAYOUT_SET_SELECTION: @@ -1565,31 +1560,14 @@ public class ListView extends AbsListView { setSelectedPositionInt(mNextSelectedPosition); - // Remember which child, if any, had accessibility focus. This must - // occur before recycling any views, since that will clear - // accessibility focus. - // TODO: This should rely on transient state. - final ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl != null) { - final View accessFocusedView = viewRootImpl.getAccessibilityFocusedHost(); - if (accessFocusedView != null) { - final View accessFocusedChild = findAccessibilityFocusedChild( - accessFocusedView); - if (accessFocusedChild != null) { - if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) { - // If the views won't be changing, try to maintain - // focus on the current view host and (if - // applicable) its virtual view. - accessibilityFocusLayoutRestoreView = accessFocusedView; - accessibilityFocusLayoutRestoreNode = viewRootImpl - .getAccessibilityFocusedVirtualView(); - } else { - // Otherwise, try to maintain focus at the same - // position. - accessibilityFocusPosition = getPositionForView(accessFocusedChild); - } - } - } + // Remember which child, if any, had accessibility focus. + final int accessibilityFocusPosition; + final View accessFocusedChild = getAccessibilityFocusedChild(); + if (accessFocusedChild != null) { + accessibilityFocusPosition = getPositionForView(accessFocusedChild); + accessFocusedChild.setHasTransientState(true); + } else { + accessibilityFocusPosition = INVALID_POSITION; } // Ensure the child containing focus, if any, has transient state. @@ -1704,25 +1682,20 @@ public class ListView extends AbsListView { } } - // Attempt to restore accessibility focus. - if (accessibilityFocusLayoutRestoreView != null) { - final AccessibilityNodeProvider provider = - accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider(); - if ((accessibilityFocusLayoutRestoreNode != null) && (provider != null)) { - final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId( - accessibilityFocusLayoutRestoreNode.getSourceNodeId()); - provider.performAction(virtualViewId, - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - } else { - accessibilityFocusLayoutRestoreView.requestAccessibilityFocus(); - } - } else if (accessibilityFocusPosition != INVALID_POSITION) { - // Bound the position within the visible children. - final int position = MathUtils.constrain( - (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1)); - final View restoreView = getChildAt(position); - if (restoreView != null) { - restoreView.requestAccessibilityFocus(); + if (accessFocusedChild != null) { + accessFocusedChild.setHasTransientState(false); + + // If we failed to maintain accessibility focus on the previous + // view, attempt to restore it to the previous position. + if (!accessFocusedChild.isAccessibilityFocused() + && accessibilityFocusPosition != INVALID_POSITION) { + // Bound the position within the visible children. + final int position = MathUtils.constrain( + accessibilityFocusPosition - mFirstPosition, 0, getChildCount() - 1); + final View restoreView = getChildAt(position); + if (restoreView != null) { + restoreView.requestAccessibilityFocus(); + } } } @@ -1754,42 +1727,31 @@ public class ListView extends AbsListView { } /** - * @param focusedView the view that has accessibility focus. - * @return the direct child that contains accessibility focus. + * @return the direct child that contains accessibility focus, or null if no + * child contains accessibility focus */ - private View findAccessibilityFocusedChild(View focusedView) { + private View getAccessibilityFocusedChild() { + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl == null) { + return null; + } + + View focusedView = viewRootImpl.getAccessibilityFocusedHost(); + if (focusedView == null) { + return null; + } + ViewParent viewParent = focusedView.getParent(); while ((viewParent instanceof View) && (viewParent != this)) { focusedView = (View) viewParent; viewParent = viewParent.getParent(); } + if (!(viewParent instanceof View)) { return null; } - return focusedView; - } - /** - * @param child a direct child of this list. - * @return Whether child is a header or footer view. - */ - private boolean isDirectChildHeaderOrFooter(View child) { - - final ArrayList<FixedViewInfo> headers = mHeaderViewInfos; - final int numHeaders = headers.size(); - for (int i = 0; i < numHeaders; i++) { - if (child == headers.get(i).view) { - return true; - } - } - final ArrayList<FixedViewInfo> footers = mFooterViewInfos; - final int numFooters = footers.size(); - for (int i = 0; i < numFooters; i++) { - if (child == footers.get(i).view) { - return true; - } - } - return false; + return focusedView; } /** diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 7c7df96..2531aa6 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -198,7 +198,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { } mPopup = popup; - mForwardingListener = new ForwardingListener(context) { + mForwardingListener = new ForwardingListener(this) { @Override public ListPopupWindow getPopup() { return popup; diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 5d0a603..a47712b 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -565,7 +565,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter setVisibility(VISIBLE); setEnabled(true); - setOnTouchListener(new ForwardingListener(context) { + setOnTouchListener(new ForwardingListener(this) { @Override public ListPopupWindow getPopup() { if (mOverflowPopup == null) { diff --git a/core/java/com/android/internal/widget/AutoScrollHelper.java b/core/java/com/android/internal/widget/AutoScrollHelper.java new file mode 100644 index 0000000..f728e6a --- /dev/null +++ b/core/java/com/android/internal/widget/AutoScrollHelper.java @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.content.res.Resources; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.AbsListView; + +/** + * AutoScrollHelper is a utility class for adding automatic edge-triggered + * scrolling to Views. + * <p> + * <b>Note:</b> Implementing classes are responsible for overriding the + * {@link #onScrollBy} method to scroll the target view. See + * {@link AbsListViewAutoScroller} for an {@link android.widget.AbsListView} + * -specific implementation. + * <p> + * <h1>Activation</h1> Automatic scrolling starts when the user touches within + * an activation area. By default, activation areas are defined as the top, + * left, right, and bottom 20% of the host view's total area. Touching within + * the top activation area scrolls up, left scrolls to the left, and so on. + * <p> + * As the user touches closer to the extreme edge of the activation area, + * scrolling accelerates up to a maximum velocity. When using the default edge + * type, {@link #EDGE_TYPE_INSIDE_EXTEND}, moving outside of the view bounds + * will scroll at the maximum velocity. + * <p> + * The following activation properties may be configured: + * <ul> + * <li>Delay after entering activation area before auto-scrolling begins, see + * {@link #setActivationDelay}. Default value is + * {@link ViewConfiguration#getTapTimeout()} to avoid conflicting with taps. + * <li>Location of activation areas, see {@link #setEdgeType}. Default value is + * {@link #EDGE_TYPE_INSIDE_EXTEND}. + * <li>Size of activation areas relative to view size, see + * {@link #setRelativeEdges}. Default value is 20% for both vertical and + * horizontal edges. + * <li>Maximum size used to constrain relative size, see + * {@link #setMaximumEdges}. Default value is {@link #NO_MAX}. + * </ul> + * <h1>Scrolling</h1> When automatic scrolling is active, the helper will + * repeatedly call {@link #onScrollBy} to apply new scrolling offsets. + * <p> + * The following scrolling properties may be configured: + * <ul> + * <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default + * value is 2.5 seconds. + * <li>Target velocity relative to view size, see {@link #setRelativeVelocity}. + * Default value is 100% per second for both vertical and horizontal. + * <li>Minimum velocity used to constrain relative velocity, see + * {@link #setMinimumVelocity}. When set, scrolling will accelerate to the + * larger of either this value or the relative target value. Default value is + * approximately 5 centimeters or 315 dips per second. + * <li>Maximum velocity used to constrain relative velocity, see + * {@link #setMaximumVelocity}. Default value is approximately 25 centimeters or + * 1575 dips per second. + * </ul> + */ +public abstract class AutoScrollHelper implements View.OnTouchListener { + /** + * Constant passed to {@link #setRelativeEdges} or + * {@link #setRelativeVelocity}. Using this value ensures that the computed + * relative value is ignored and the absolute maximum value is always used. + */ + public static final float RELATIVE_UNSPECIFIED = 0; + + /** + * Constant passed to {@link #setMaximumEdges}, {@link #setMaximumVelocity}, + * or {@link #setMinimumVelocity}. Using this value ensures that the + * computed relative value is always used without constraining to a + * particular minimum or maximum value. + */ + public static final float NO_MAX = Float.MAX_VALUE; + + /** + * Constant passed to {@link #setMaximumEdges}, or + * {@link #setMaximumVelocity}, or {@link #setMinimumVelocity}. Using this + * value ensures that the computed relative value is always used without + * constraining to a particular minimum or maximum value. + */ + public static final float NO_MIN = 0; + + /** + * Edge type that specifies an activation area starting at the view bounds + * and extending inward. Moving outside the view bounds will stop scrolling. + * + * @see #setEdgeType + */ + public static final int EDGE_TYPE_INSIDE = 0; + + /** + * Edge type that specifies an activation area starting at the view bounds + * and extending inward. After activation begins, moving outside the view + * bounds will continue scrolling. + * + * @see #setEdgeType + */ + public static final int EDGE_TYPE_INSIDE_EXTEND = 1; + + /** + * Edge type that specifies an activation area starting at the view bounds + * and extending outward. Moving inside the view bounds will stop scrolling. + * + * @see #setEdgeType + */ + public static final int EDGE_TYPE_OUTSIDE = 2; + + private static final int HORIZONTAL = 0; + private static final int VERTICAL = 1; + + /** Scroller used to control acceleration toward maximum velocity. */ + private final ClampedScroller mScroller = new ClampedScroller(); + + /** Interpolator used to scale velocity with touch position. */ + private final Interpolator mEdgeInterpolator = new AccelerateInterpolator(); + + /** The view to auto-scroll. Might not be the source of touch events. */ + private final View mTarget; + + /** Runnable used to animate scrolling. */ + private Runnable mRunnable; + + /** Edge insets used to activate auto-scrolling. */ + private float[] mRelativeEdges = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED }; + + /** Clamping values for edge insets used to activate auto-scrolling. */ + private float[] mMaximumEdges = new float[] { NO_MAX, NO_MAX }; + + /** The type of edge being used. */ + private int mEdgeType; + + /** Delay after entering an activation edge before auto-scrolling begins. */ + private int mActivationDelay; + + /** Relative scrolling velocity at maximum edge distance. */ + private float[] mRelativeVelocity = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED }; + + /** Clamping values used for scrolling velocity. */ + private float[] mMinimumVelocity = new float[] { NO_MIN, NO_MIN }; + + /** Clamping values used for scrolling velocity. */ + private float[] mMaximumVelocity = new float[] { NO_MAX, NO_MAX }; + + /** Whether to start activation immediately. */ + private boolean mSkipDelay; + + /** Whether to reset the scroller start time on the next animation. */ + private boolean mResetScroller; + + /** Whether the auto-scroller is active. */ + private boolean mActive; + + /** Whether the auto-scroller is scrolling. */ + private boolean mScrolling; + + /** Whether the auto-scroller is enabled. */ + private boolean mEnabled; + + /** Whether the auto-scroller consumes events when scrolling. */ + private boolean mExclusiveEnabled; + + /** Down time of the most recent down touch event. */ + private long mDownTime; + + // Default values. + private static final int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND; + private static final int DEFAULT_MINIMUM_VELOCITY_DIPS = 315; + private static final int DEFAULT_MAXIMUM_VELOCITY_DIPS = 1575; + private static final float DEFAULT_MAXIMUM_EDGE = NO_MAX; + private static final float DEFAULT_RELATIVE_EDGE = 0.2f; + private static final float DEFAULT_RELATIVE_VELOCITY = 1f; + private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout(); + private static final int DEFAULT_RAMP_UP_DURATION = 2500; + // TODO: RAMP_DOWN_DURATION of 500ms? + + /** + * Creates a new helper for scrolling the specified target view. + * <p> + * The resulting helper may be configured by chaining setter calls and + * should be set as a touch listener on the target view. + * <p> + * By default, the helper is disabled and will not respond to touch events + * until it is enabled using {@link #setEnabled}. + * + * @param target The view to automatically scroll. + */ + public AutoScrollHelper(View target) { + mTarget = target; + + final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); + final int maxVelocity = (int) (DEFAULT_MAXIMUM_VELOCITY_DIPS * metrics.density + 0.5f); + final int minVelocity = (int) (DEFAULT_MINIMUM_VELOCITY_DIPS * metrics.density + 0.5f); + setMaximumVelocity(maxVelocity, maxVelocity); + setMinimumVelocity(minVelocity, minVelocity); + + setEdgeType(DEFAULT_EDGE_TYPE); + setMaximumEdges(DEFAULT_MAXIMUM_EDGE, DEFAULT_MAXIMUM_EDGE); + setRelativeEdges(DEFAULT_RELATIVE_EDGE, DEFAULT_RELATIVE_EDGE); + setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY); + setActivationDelay(DEFAULT_ACTIVATION_DELAY); + setRampUpDuration(DEFAULT_RAMP_UP_DURATION); + + mEnabled = true; + } + + /** + * Sets whether the scroll helper is enabled and should respond to touch + * events. + * + * @param enabled Whether the scroll helper is enabled. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setEnabled(boolean enabled) { + if (!enabled) { + stop(true); + } + + mEnabled = enabled; + return this; + } + + /** + * @return True if this helper is enabled and responding to touch events. + */ + public boolean isEnabled() { + return mEnabled; + } + + /** + * Enables or disables exclusive handling of touch events during scrolling. + * By default, exclusive handling is disabled and the target view receives + * all touch events. + * <p> + * When enabled, {@link #onTouch} will return true if the helper is + * currently scrolling and false otherwise. + * + * @param enabled True to exclusively handle touch events during scrolling, + * false to allow the target view to receive all touch events. + * @see #isExclusiveEnabled() + * @see #onTouch(View, MotionEvent) + */ + public void setExclusiveEnabled(boolean enabled) { + mExclusiveEnabled = enabled; + } + + /** + * Indicates whether the scroll helper handles touch events exclusively + * during scrolling. + * + * @return True if exclusive handling of touch events during scrolling is + * enabled, false otherwise. + * @see #setExclusiveEnabled(boolean) + */ + public boolean isExclusiveEnabled() { + return mExclusiveEnabled; + } + + /** + * Sets the absolute maximum scrolling velocity. + * <p> + * If relative velocity is not specified, scrolling will always reach the + * same maximum velocity. If both relative and maximum velocities are + * specified, the maximum velocity will be used to clamp the calculated + * relative velocity. + * + * @param horizontalMax The maximum horizontal scrolling velocity, or + * {@link #NO_MAX} to leave the relative value unconstrained. + * @param verticalMax The maximum vertical scrolling velocity, or + * {@link #NO_MAX} to leave the relative value unconstrained. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) { + mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f; + mMaximumVelocity[VERTICAL] = verticalMax / 1000f; + return this; + } + + /** + * Sets the absolute minimum scrolling velocity. + * <p> + * If both relative and minimum velocities are specified, the minimum + * velocity will be used to clamp the calculated relative velocity. + * + * @param horizontalMin The minimum horizontal scrolling velocity, or + * {@link #NO_MIN} to leave the relative value unconstrained. + * @param verticalMin The minimum vertical scrolling velocity, or + * {@link #NO_MIN} to leave the relative value unconstrained. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) { + mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f; + mMinimumVelocity[VERTICAL] = verticalMin / 1000f; + return this; + } + + /** + * Sets the target scrolling velocity relative to the host view's + * dimensions. + * <p> + * If both relative and maximum velocities are specified, the maximum + * velocity will be used to clamp the calculated relative velocity. + * + * @param horizontal The target horizontal velocity as a fraction of the + * host view width per second, or {@link #RELATIVE_UNSPECIFIED} + * to ignore. + * @param vertical The target vertical velocity as a fraction of the host + * view height per second, or {@link #RELATIVE_UNSPECIFIED} to + * ignore. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) { + mRelativeVelocity[HORIZONTAL] = horizontal / 1000f; + mRelativeVelocity[VERTICAL] = vertical / 1000f; + return this; + } + + /** + * Sets the activation edge type, one of: + * <ul> + * <li>{@link #EDGE_TYPE_INSIDE} for edges that respond to touches inside + * the bounds of the host view. If touch moves outside the bounds, scrolling + * will stop. + * <li>{@link #EDGE_TYPE_INSIDE_EXTEND} for inside edges that continued to + * scroll when touch moves outside the bounds of the host view. + * <li>{@link #EDGE_TYPE_OUTSIDE} for edges that only respond to touches + * that move outside the bounds of the host view. + * </ul> + * + * @param type The type of edge to use. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setEdgeType(int type) { + mEdgeType = type; + return this; + } + + /** + * Sets the activation edge size relative to the host view's dimensions. + * <p> + * If both relative and maximum edges are specified, the maximum edge will + * be used to constrain the calculated relative edge size. + * + * @param horizontal The horizontal edge size as a fraction of the host view + * width, or {@link #RELATIVE_UNSPECIFIED} to always use the + * maximum value. + * @param vertical The vertical edge size as a fraction of the host view + * height, or {@link #RELATIVE_UNSPECIFIED} to always use the + * maximum value. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) { + mRelativeEdges[HORIZONTAL] = horizontal; + mRelativeEdges[VERTICAL] = vertical; + return this; + } + + /** + * Sets the absolute maximum edge size. + * <p> + * If relative edge size is not specified, activation edges will always be + * the maximum edge size. If both relative and maximum edges are specified, + * the maximum edge will be used to constrain the calculated relative edge + * size. + * + * @param horizontalMax The maximum horizontal edge size in pixels, or + * {@link #NO_MAX} to use the unconstrained calculated relative + * value. + * @param verticalMax The maximum vertical edge size in pixels, or + * {@link #NO_MAX} to use the unconstrained calculated relative + * value. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) { + mMaximumEdges[HORIZONTAL] = horizontalMax; + mMaximumEdges[VERTICAL] = verticalMax; + return this; + } + + /** + * Sets the delay after entering an activation edge before activation of + * auto-scrolling. By default, the activation delay is set to + * {@link ViewConfiguration#getTapTimeout()}. + * <p> + * Specifying a delay of zero will start auto-scrolling immediately after + * the touch position enters an activation edge. + * + * @param delayMillis The activation delay in milliseconds. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setActivationDelay(int delayMillis) { + mActivationDelay = delayMillis; + return this; + } + + /** + * Sets the amount of time after activation of auto-scrolling that is takes + * to reach target velocity for the current touch position. + * <p> + * Specifying a duration greater than zero prevents sudden jumps in + * velocity. + * + * @param durationMillis The ramp-up duration in milliseconds. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setRampUpDuration(int durationMillis) { + mScroller.setDuration(durationMillis); + return this; + } + + /** + * Handles touch events by activating automatic scrolling, adjusting scroll + * velocity, or stopping. + * <p> + * If {@link #isExclusiveEnabled()} is false, always returns false so that + * the host view may handle touch events. Otherwise, returns true when + * automatic scrolling is active and false otherwise. + */ + @Override + public boolean onTouch(View v, MotionEvent event) { + if (!mEnabled) { + return false; + } + + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownTime = event.getDownTime(); + case MotionEvent.ACTION_MOVE: + final float xValue = getEdgeValue(mRelativeEdges[HORIZONTAL], v.getWidth(), + mMaximumEdges[HORIZONTAL], event.getX()); + final float yValue = getEdgeValue(mRelativeEdges[VERTICAL], v.getHeight(), + mMaximumEdges[VERTICAL], event.getY()); + final float maxVelX = constrain(mRelativeVelocity[HORIZONTAL] * mTarget.getWidth(), + mMinimumVelocity[HORIZONTAL], mMaximumVelocity[HORIZONTAL]); + final float maxVelY = constrain(mRelativeVelocity[VERTICAL] * mTarget.getHeight(), + mMinimumVelocity[VERTICAL], mMaximumVelocity[VERTICAL]); + mScroller.setTargetVelocity(xValue * maxVelX, yValue * maxVelY); + + if ((xValue != 0 || yValue != 0) && !mActive) { + mActive = true; + mResetScroller = true; + if (mRunnable == null) { + mRunnable = new AutoScrollRunnable(); + } + if (mSkipDelay) { + mTarget.postOnAnimation(mRunnable); + } else { + mSkipDelay = true; + mTarget.postOnAnimationDelayed(mRunnable, mActivationDelay); + } + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + stop(true); + break; + } + + return mExclusiveEnabled && mScrolling; + } + + /** + * Override this method to scroll the target view by the specified number + * of pixels. + * <p> + * Returns whether the target view was able to scroll the requested amount. + * + * @param deltaX The amount to scroll in the X direction, in pixels. + * @param deltaY The amount to scroll in the Y direction, in pixels. + * @return true if the target view was able to scroll the requested amount. + */ + public abstract boolean onScrollBy(int deltaX, int deltaY); + + /** + * Returns the interpolated position of a touch point relative to an edge + * defined by its relative inset, its maximum absolute inset, and the edge + * interpolator. + * + * @param relativeValue The size of the inset relative to the total size. + * @param size Total size. + * @param maxValue The maximum size of the inset, used to clamp (relative * + * total). + * @param current Touch position within within the total size. + * @return Interpolated value of the touch position within the edge. + */ + private float getEdgeValue(float relativeValue, float size, float maxValue, float current) { + // For now, leading and trailing edges are always the same size. + final float edgeSize = constrain(relativeValue * size, NO_MIN, maxValue); + final float valueLeading = constrainEdgeValue(current, edgeSize); + final float valueTrailing = constrainEdgeValue(size - current, edgeSize); + final float value = (valueTrailing - valueLeading); + final float interpolated; + if (value < 0) { + interpolated = -mEdgeInterpolator.getInterpolation(-value); + } else if (value > 0) { + interpolated = mEdgeInterpolator.getInterpolation(value); + } else { + return 0; + } + + return constrain(interpolated, -1, 1); + } + + private float constrainEdgeValue(float current, float leading) { + if (leading == 0) { + return 0; + } + + switch (mEdgeType) { + case EDGE_TYPE_INSIDE: + case EDGE_TYPE_INSIDE_EXTEND: + if (current < leading) { + if (current > 0) { + // Movement up to the edge is scaled. + return 1f - current / leading; + } else if (mActive && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) { + // Movement beyond the edge is always maximum. + return 1f; + } + } + break; + case EDGE_TYPE_OUTSIDE: + if (current < 0) { + // Movement beyond the edge is scaled. + return current / -leading; + } + break; + } + + return 0; + } + + private static float constrain(float value, float min, float max) { + if (value > max) { + return max; + } else if (value < min) { + return min; + } else { + return value; + } + } + + /** + * Stops auto-scrolling immediately, optionally reseting the auto-scrolling + * delay. + * + * @param reset Whether to reset the auto-scrolling delay. + */ + private void stop(boolean reset) { + mActive = false; + mScrolling = false; + mSkipDelay = !reset; + + if (mRunnable != null) { + mTarget.removeCallbacks(mRunnable); + } + } + + /** + * Sends a {@link MotionEvent#ACTION_CANCEL} event to the target view, + * canceling any ongoing touch events. + */ + private void cancelTargetTouch() { + final MotionEvent cancel = MotionEvent.obtain( + mDownTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0); + cancel.setAction(MotionEvent.ACTION_CANCEL); + mTarget.onTouchEvent(cancel); + cancel.recycle(); + } + + private class AutoScrollRunnable implements Runnable { + @Override + public void run() { + if (!mActive) { + return; + } + + if (mResetScroller) { + mResetScroller = false; + mScroller.start(); + } + + final View target = mTarget; + final ClampedScroller scroller = mScroller; + scroller.computeScrollDelta(); + + final int deltaX = scroller.getDeltaX(); + final int deltaY = scroller.getDeltaY(); + if ((deltaX != 0 || deltaY != 0 || !scroller.isFinished()) + && onScrollBy(deltaX, deltaY)) { + // Update whether we're actively scrolling. + final boolean scrolling = (deltaX != 0 || deltaY != 0); + if (mScrolling != scrolling) { + mScrolling = scrolling; + + // If we just started actively scrolling, make sure any down + // or move events send to the target view are canceled. + if (mExclusiveEnabled && scrolling) { + cancelTargetTouch(); + } + } + + // Keep going until the scroller has permanently stopped or the + // view can't scroll any more. If the user moves their finger + // again, we'll repost the animation. + target.postOnAnimation(this); + } else { + stop(false); + } + } + } + + /** + * Scroller whose velocity follows the curve of an {@link Interpolator} and + * is clamped to the interpolated 0f value before starting and the + * interpolated 1f value after a specified duration. + */ + private static class ClampedScroller { + private final Interpolator mInterpolator = new AccelerateInterpolator(); + + private int mDuration; + private float mTargetVelocityX; + private float mTargetVelocityY; + + private long mStartTime; + private long mDeltaTime; + private int mDeltaX; + private int mDeltaY; + + /** + * Creates a new ramp-up scroller that reaches full velocity after a + * specified duration. + */ + public ClampedScroller() { + reset(); + } + + public void setDuration(int durationMillis) { + mDuration = durationMillis; + } + + /** + * Starts the scroller at the current animation time. + */ + public void start() { + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDeltaTime = mStartTime; + } + + /** + * Returns whether the scroller is finished, which means that its + * acceleration is zero. + * + * @return Whether the scroller is finished. + */ + public boolean isFinished() { + if (mTargetVelocityX == 0 && mTargetVelocityY == 0) { + return true; + } + final long currentTime = AnimationUtils.currentAnimationTimeMillis(); + final long elapsedSinceStart = currentTime - mStartTime; + return elapsedSinceStart > mDuration; + } + + /** + * Stops the scroller and resets its values. + */ + public void reset() { + mStartTime = -1; + mDeltaTime = -1; + mDeltaX = 0; + mDeltaY = 0; + } + + /** + * Computes the current scroll deltas. This usually only be called after + * starting the scroller with {@link #start()}. + * + * @see #getDeltaX() + * @see #getDeltaY() + */ + public void computeScrollDelta() { + final long currentTime = AnimationUtils.currentAnimationTimeMillis(); + final long elapsedSinceStart = currentTime - mStartTime; + final float value; + if (mStartTime < 0) { + value = 0f; + } else if (elapsedSinceStart < mDuration) { + value = (float) elapsedSinceStart / mDuration; + } else { + value = 1f; + } + + final float scale = mInterpolator.getInterpolation(value); + final long elapsedSinceDelta = currentTime - mDeltaTime; + + mDeltaTime = currentTime; + mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX); + mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY); + } + + /** + * Sets the target velocity for this scroller. + * + * @param x The target X velocity in pixels per millisecond. + * @param y The target Y velocity in pixels per millisecond. + */ + public void setTargetVelocity(float x, float y) { + mTargetVelocityX = x; + mTargetVelocityY = y; + } + + /** + * The distance traveled in the X-coordinate computed by the last call + * to {@link #computeScrollDelta()}. + */ + public int getDeltaX() { + return mDeltaX; + } + + /** + * The distance traveled in the Y-coordinate computed by the last call + * to {@link #computeScrollDelta()}. + */ + public int getDeltaY() { + return mDeltaY; + } + } + + /** + * Implementation of {@link AutoScrollHelper} that knows how to scroll + * generic {@link AbsListView}s. + */ + public static class AbsListViewAutoScroller extends AutoScrollHelper { + private final AbsListView mTarget; + + public AbsListViewAutoScroller(AbsListView target) { + super(target); + mTarget = target; + } + + @Override + public boolean onScrollBy(int deltaX, int deltaY) { + return mTarget.scrollListBy(deltaY); + } + } +} 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); + } + } |
