summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/bluetooth/BluetoothAssignedNumbers.java650
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java45
-rwxr-xr-xcore/java/android/bluetooth/IBluetoothHeadset.aidl3
-rw-r--r--core/java/android/content/ContentProvider.java227
-rw-r--r--core/java/android/content/ContentProviderClient.java63
-rw-r--r--core/java/android/content/ContentProviderNative.java30
-rw-r--r--core/java/android/content/ContentResolver.java199
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/IContentProvider.java8
-rw-r--r--core/java/android/content/Intent.java21
-rw-r--r--core/java/android/database/Cursor.java5
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java66
-rw-r--r--core/java/android/hardware/display/DisplayManager.java18
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java16
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl6
-rw-r--r--core/java/android/hardware/display/WifiDisplaySessionInfo.java116
-rw-r--r--core/java/android/hardware/display/WifiDisplayStatus.java28
-rw-r--r--core/java/android/net/LinkInfo.java14
-rw-r--r--core/java/android/net/SamplingDataTracker.java93
-rw-r--r--core/java/android/net/WifiLinkInfo.java12
-rw-r--r--core/java/android/os/RemoteCallbackList.java2
-rw-r--r--core/java/android/print/IPrintManager.aidl10
-rw-r--r--core/java/android/print/IPrintSpooler.aidl6
-rw-r--r--core/java/android/print/IPrintSpoolerClient.aidl8
-rw-r--r--core/java/android/print/IPrinterDiscoveryObserver.aidl32
-rw-r--r--core/java/android/print/PrintManager.java7
-rw-r--r--core/java/android/print/PrinterDiscoverySession.java297
-rw-r--r--core/java/android/provider/DocumentsContract.java197
-rw-r--r--core/java/android/provider/Settings.java74
-rw-r--r--core/java/android/speech/hotword/HotwordRecognitionListener.java60
-rw-r--r--core/java/android/speech/hotword/HotwordRecognitionService.java258
-rw-r--r--core/java/android/speech/hotword/HotwordRecognizer.java409
-rw-r--r--core/java/android/speech/hotword/IHotwordRecognitionListener.aidl60
-rw-r--r--core/java/android/speech/hotword/IHotwordRecognitionService.aidl47
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/ViewParent.java17
-rw-r--r--core/java/android/view/transition/Visibility.java104
-rw-r--r--core/java/android/widget/ListPopupWindow.java119
-rw-r--r--core/java/android/widget/ListView.java112
-rw-r--r--core/java/android/widget/Spinner.java2
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuPresenter.java2
-rw-r--r--core/java/com/android/internal/widget/AutoScrollHelper.java768
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java56
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);
+ }
+
}