summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/ActivityManager.java1
-rw-r--r--core/java/android/app/ContextImpl.java6
-rw-r--r--core/java/android/bluetooth/BluetoothAssignedNumbers.java650
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java8
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java45
-rwxr-xr-xcore/java/android/bluetooth/IBluetoothHeadset.aidl3
-rw-r--r--core/java/android/content/ContentProviderClient.java7
-rw-r--r--core/java/android/content/ContentResolver.java26
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/Intent.java10
-rw-r--r--core/java/android/content/PeriodicSync.java71
-rw-r--r--core/java/android/content/SyncRequest.java193
-rw-r--r--core/java/android/content/SyncService.java169
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl8
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/ddm/DdmHandleProfiling.java54
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java42
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceUser.aidl2
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java80
-rw-r--r--core/java/android/net/CaptivePortalTracker.java133
-rw-r--r--core/java/android/net/ConnectivityManager.java89
-rw-r--r--core/java/android/net/IConnectivityManager.aidl5
-rw-r--r--core/java/android/net/LinkInfo.java14
-rw-r--r--core/java/android/net/MobileDataStateTracker.java94
-rw-r--r--core/java/android/net/NetworkInfo.java7
-rw-r--r--core/java/android/net/SamplingDataTracker.java93
-rw-r--r--core/java/android/net/WifiLinkInfo.java12
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl1
-rw-r--r--core/java/android/nfc/NfcActivityManager.java61
-rw-r--r--core/java/android/nfc/NfcAdapter.java82
-rw-r--r--core/java/android/nfc/cardemulation/ApduServiceInfo.java34
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulation.java343
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulationManager.java105
-rw-r--r--core/java/android/nfc/cardemulation/HostApduService.java33
-rw-r--r--core/java/android/nfc/cardemulation/OffHostApduService.java5
-rw-r--r--core/java/android/os/Bundle.java8
-rw-r--r--core/java/android/os/Debug.java13
-rw-r--r--core/java/android/print/IPrintManager.aidl4
-rw-r--r--core/java/android/print/PageRange.java2
-rw-r--r--core/java/android/print/PrintAttributes.java60
-rw-r--r--core/java/android/print/PrintDocumentAdapter.java40
-rw-r--r--core/java/android/print/PrintDocumentInfo.java285
-rw-r--r--core/java/android/print/PrintFileDocumentAdapter.java10
-rw-r--r--core/java/android/print/PrintJobInfo.java62
-rw-r--r--core/java/android/print/PrintManager.java13
-rw-r--r--core/java/android/print/PrinterCapabilitiesInfo.java4
-rw-r--r--core/java/android/print/PrinterDiscoverySession.java35
-rw-r--r--core/java/android/print/PrinterInfo.java29
-rw-r--r--core/java/android/print/pdf/PdfDocument.java43
-rw-r--r--core/java/android/print/pdf/PrintedPdfDocument.java165
-rw-r--r--core/java/android/printservice/IPrintService.aidl4
-rw-r--r--core/java/android/printservice/PrintDocument.java11
-rw-r--r--core/java/android/printservice/PrintJob.java71
-rw-r--r--core/java/android/printservice/PrintService.java44
-rw-r--r--core/java/android/printservice/PrinterDiscoverySession.java102
-rw-r--r--core/java/android/provider/AlarmClock.java61
-rw-r--r--core/java/android/provider/DocumentsContract.java812
-rw-r--r--core/java/android/provider/DocumentsProvider.java419
-rw-r--r--core/java/android/provider/Settings.java136
-rw-r--r--core/java/android/security/IKeystoreService.java16
-rw-r--r--core/java/android/speech/hotword/HotwordRecognitionListener.java60
-rw-r--r--core/java/android/speech/hotword/HotwordRecognitionService.java235
-rw-r--r--core/java/android/speech/hotword/HotwordRecognizer.java393
-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/speech/tts/SynthesisRequest.java2
-rw-r--r--core/java/android/util/LayoutDirection.java8
-rw-r--r--core/java/android/view/ScaleGestureDetector.java132
-rw-r--r--core/java/android/view/View.java64
-rw-r--r--core/java/android/view/ViewGroup.java5
-rw-r--r--core/java/android/view/ViewParent.java17
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java206
-rw-r--r--core/java/android/view/accessibility/CaptioningManager.java300
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java10
-rw-r--r--core/java/android/view/transition/Move.java2
-rw-r--r--core/java/android/view/transition/TextChange.java35
-rw-r--r--core/java/android/view/transition/Transition.java32
-rw-r--r--core/java/android/view/transition/TransitionManager.java8
-rw-r--r--core/java/android/view/transition/Visibility.java106
-rw-r--r--core/java/android/webkit/CallbackProxy.java43
-rw-r--r--core/java/android/webkit/DebugFlags.java18
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java5
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java5
-rw-r--r--core/java/android/webkit/WebView.java83
-rw-r--r--core/java/android/webkit/WebViewClassic.java9
-rw-r--r--core/java/android/webkit/WebViewProvider.java8
-rw-r--r--core/java/android/widget/AbsListView.java67
-rw-r--r--core/java/android/widget/ActivityChooserView.java2
-rw-r--r--core/java/android/widget/CalendarView.java21
-rw-r--r--core/java/android/widget/GridLayout.java124
-rw-r--r--core/java/android/widget/GridView.java35
-rw-r--r--core/java/android/widget/ListPopupWindow.java146
-rw-r--r--core/java/android/widget/ListView.java129
-rw-r--r--core/java/android/widget/PopupMenu.java30
-rw-r--r--core/java/android/widget/Spinner.java4
-rw-r--r--core/java/android/widget/TextView.java5
-rw-r--r--core/java/android/widget/TimePicker.java14
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java90
-rw-r--r--core/java/com/android/internal/notification/DemoContactNotificationScorer.java4
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java1
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuPresenter.java4
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java2
-rw-r--r--core/java/com/android/internal/widget/AutoScrollHelper.java924
103 files changed, 6687 insertions, 1766 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6ac2e80..7c40bb1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -490,6 +490,7 @@ public class ActivityManager {
/**
* The id of the ActivityStack this Task was on most recently.
+ * @hide
*/
public int stackId;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cdec399..f10290d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -105,6 +105,7 @@ import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.CaptioningManager;
import android.view.inputmethod.InputMethodManager;
import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
@@ -307,6 +308,11 @@ class ContextImpl extends Context {
return AccessibilityManager.getInstance(ctx);
}});
+ registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
+ public Object getService(ContextImpl ctx) {
+ return new CaptioningManager(ctx);
+ }});
+
registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
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/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index b390aa1..a2bb78c 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -702,6 +702,10 @@ public final class BluetoothGatt implements BluetoothProfile {
* @param start Start or stop advertising
*/
/*package*/ void listen(boolean start) {
+ if (mContext == null || !mContext.getResources().
+ getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) {
+ throw new UnsupportedOperationException("BluetoothGatt#listen is blocked");
+ }
if (DBG) Log.d(TAG, "listen() - start: " + start);
if (mService == null || mClientIf == 0) return;
@@ -728,6 +732,10 @@ public final class BluetoothGatt implements BluetoothProfile {
/*package*/ void setAdvData(boolean advData, boolean includeName, boolean includeTxPower,
Integer minInterval, Integer maxInterval,
Integer appearance, Byte[] manufacturerData) {
+ if (mContext == null || !mContext.getResources().
+ getBoolean(com.android.internal.R.bool.config_bluetooth_le_peripheral_mode_supported)) {
+ throw new UnsupportedOperationException("BluetoothGatt#setAdvData is blocked");
+ }
if (DBG) Log.d(TAG, "setAdvData()");
if (mService == null || mClientIf == 0) return;
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/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 024a521..4e8dd82 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -316,4 +316,11 @@ public class ContentProviderClient {
public ContentProvider getLocalContentProvider() {
return ContentProvider.coerceToLocalContentProvider(mContentProvider);
}
+
+ /** {@hide} */
+ public static void closeQuietly(ContentProviderClient client) {
+ if (client != null) {
+ client.release();
+ }
+ }
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index a761a89..8a5a56c 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -141,7 +141,7 @@ public abstract class ContentResolver {
public static final String SYNC_EXTRAS_PRIORITY = "sync_priority";
/** {@hide} Flag to allow sync to occur on metered network. */
- public static final String SYNC_EXTRAS_ALLOW_METERED = "allow_metered";
+ public static final String SYNC_EXTRAS_DISALLOW_METERED = "disallow_metered";
/**
* Set by the SyncManager to request that the SyncAdapter initialize itself for
@@ -1669,7 +1669,7 @@ public abstract class ContentResolver {
new SyncRequest.Builder()
.setSyncAdapter(account, authority)
.setExtras(extras)
- .syncOnce(0, 0) // Immediate sync.
+ .syncOnce()
.build();
requestSync(request);
}
@@ -1677,6 +1677,9 @@ public abstract class ContentResolver {
/**
* Register a sync with the SyncManager. These requests are built using the
* {@link SyncRequest.Builder}.
+ *
+ * @param request The immutable SyncRequest object containing the sync parameters. Use
+ * {@link SyncRequest.Builder} to construct these.
*/
public static void requestSync(SyncRequest request) {
try {
@@ -1812,6 +1815,9 @@ public abstract class ContentResolver {
* {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE},
* {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true.
* If any are supplied then an {@link IllegalArgumentException} will be thrown.
+ * <p>As of API level 19 this function introduces a default flexibility of ~4% (up to a maximum
+ * of one hour in the day) into the requested period. Use
+ * {@link SyncRequest.Builder#syncPeriodic(long, long)} to set this flexibility manually.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -1875,22 +1881,6 @@ public abstract class ContentResolver {
}
/**
- * Remove the specified sync. This will remove any syncs that have been scheduled to run, but
- * will not cancel any running syncs.
- * <p>This method requires the caller to hold the permission</p>
- * If the request is for a periodic sync this will cancel future occurrences of the sync.
- *
- * It is possible to cancel a sync using a SyncRequest object that is different from the object
- * with which you requested the sync. Do so by building a SyncRequest with exactly the same
- * service/adapter, frequency, <b>and</b> extras bundle.
- *
- * @param request SyncRequest object containing information about sync to cancel.
- */
- public static void cancelSync(SyncRequest request) {
- // TODO: Finish this implementation.
- }
-
- /**
* Get the list of information about the periodic syncs for the given account and authority.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cd1f87b..2ff9182 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2011,6 +2011,17 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.accessibility.CaptioningManager} for obtaining
+ * captioning properties and listening for changes in captioning
+ * preferences.
+ *
+ * @see #getSystemService
+ * @see android.view.accessibility.CaptioningManager
+ */
+ public static final String CAPTIONING_SERVICE = "captioning";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.
*
* @see #getSystemService
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c99f09c..dfc0412 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2687,10 +2687,6 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
- /** {@hide} */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_MANAGE_DOCUMENT = "android.intent.action.MANAGE_DOCUMENT";
-
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -3304,8 +3300,10 @@ public class Intent implements Parcelable, Cloneable {
/**
* Optional extra for {@link #ACTION_SHUTDOWN} that allows the sender to qualify that
* this shutdown is only for the user space of the system, not a complete shutdown.
- * Hardware should not be shut down when this is true. The default if not supplied
- * is false.
+ * When this is true, hardware devices can use this information to determine that
+ * they shouldn't do a complete shutdown of their device since this is not a
+ * complete shutdown down to the kernel, but only user space restarting.
+ * The default if not supplied is false.
*/
public static final String EXTRA_SHUTDOWN_USERSPACE_ONLY
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 6aca151..b586eec 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -29,17 +29,13 @@ public class PeriodicSync implements Parcelable {
public final Account account;
/** The authority of the sync. Can be null. */
public final String authority;
- /** The service for syncing, if this is an anonymous sync. Can be null.*/
- public final ComponentName service;
/** Any extras that parameters that are to be passed to the sync adapter. */
public final Bundle extras;
/** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */
public final long period;
- /** Whether this periodic sync uses a service. */
- public final boolean isService;
/**
- * How much flexibility can be taken in scheduling the sync, in seconds.
* {@hide}
+ * How much flexibility can be taken in scheduling the sync, in seconds.
*/
public final long flexTime;
@@ -52,76 +48,44 @@ public class PeriodicSync implements Parcelable {
public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) {
this.account = account;
this.authority = authority;
- this.service = null;
- this.isService = false;
if (extras == null) {
this.extras = new Bundle();
} else {
this.extras = new Bundle(extras);
}
this.period = periodInSeconds;
- // Old API uses default flex time. No-one should be using this ctor anyway.
+ // Initialise to a sane value.
this.flexTime = 0L;
}
- // TODO: Add copy ctor from SyncRequest?
-
/**
- * Create a copy of a periodic sync.
* {@hide}
+ * Create a copy of a periodic sync.
*/
public PeriodicSync(PeriodicSync other) {
this.account = other.account;
this.authority = other.authority;
- this.service = other.service;
- this.isService = other.isService;
this.extras = new Bundle(other.extras);
this.period = other.period;
this.flexTime = other.flexTime;
}
/**
- * A PeriodicSync for a sync with a specified provider.
* {@hide}
+ * A PeriodicSync for a sync with a specified provider.
*/
public PeriodicSync(Account account, String authority, Bundle extras,
long period, long flexTime) {
this.account = account;
this.authority = authority;
- this.service = null;
- this.isService = false;
- this.extras = new Bundle(extras);
- this.period = period;
- this.flexTime = flexTime;
- }
-
- /**
- * A PeriodicSync for a sync with a specified SyncService.
- * {@hide}
- */
- public PeriodicSync(ComponentName service, Bundle extras,
- long period,
- long flexTime) {
- this.account = null;
- this.authority = null;
- this.service = service;
- this.isService = true;
this.extras = new Bundle(extras);
this.period = period;
this.flexTime = flexTime;
}
private PeriodicSync(Parcel in) {
- this.isService = (in.readInt() != 0);
- if (this.isService) {
- this.service = in.readParcelable(null);
- this.account = null;
- this.authority = null;
- } else {
- this.account = in.readParcelable(null);
- this.authority = in.readString();
- this.service = null;
- }
+ this.account = in.readParcelable(null);
+ this.authority = in.readString();
this.extras = in.readBundle();
this.period = in.readLong();
this.flexTime = in.readLong();
@@ -134,13 +98,8 @@ public class PeriodicSync implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(isService ? 1 : 0);
- if (account == null && authority == null) {
- dest.writeParcelable(service, flags);
- } else {
- dest.writeParcelable(account, flags);
- dest.writeString(authority);
- }
+ dest.writeParcelable(account, flags);
+ dest.writeString(authority);
dest.writeBundle(extras);
dest.writeLong(period);
dest.writeLong(flexTime);
@@ -167,17 +126,8 @@ public class PeriodicSync implements Parcelable {
return false;
}
final PeriodicSync other = (PeriodicSync) o;
- if (this.isService != other.isService) {
- return false;
- }
- boolean equal = false;
- if (this.isService) {
- equal = service.equals(other.service);
- } else {
- equal = account.equals(other.account)
- && authority.equals(other.authority);
- }
- return equal
+ return account.equals(other.account)
+ && authority.equals(other.authority)
&& period == other.period
&& syncExtrasEquals(extras, other.extras);
}
@@ -208,7 +158,6 @@ public class PeriodicSync implements Parcelable {
public String toString() {
return "account: " + account +
", authority: " + authority +
- ", service: " + service +
". period: " + period + "s " +
", flex: " + flexTime;
}
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 4474c70..d4e0c2a 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -20,20 +20,19 @@ import android.accounts.Account;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Pair;
public class SyncRequest implements Parcelable {
private static final String TAG = "SyncRequest";
- /** Account to pass to the sync adapter. Can be null. */
+ /** Account to pass to the sync adapter. May be null. */
private final Account mAccountToSync;
/** Authority string that corresponds to a ContentProvider. */
private final String mAuthority;
- /** {@link SyncService} identifier. */
+ /** Sync service identifier. May be null.*/
private final ComponentName mComponentInfo;
/** Bundle containing user info as well as sync settings. */
private final Bundle mExtras;
- /** Allow this sync request on metered networks. */
- private final boolean mAllowMetered;
+ /** Disallow this sync request on metered networks. */
+ private final boolean mDisallowMetered;
/**
* Anticipated upload size in bytes.
* TODO: Not yet used - we put this information into the bundle for simplicity.
@@ -70,14 +69,18 @@ public class SyncRequest implements Parcelable {
return mIsPeriodic;
}
+ /**
+ * {@hide}
+ * @return whether this is an expedited sync.
+ */
public boolean isExpedited() {
return mIsExpedited;
}
/**
* {@hide}
- * @return true if this sync uses an account/authority pair, or false if
- * this is an anonymous sync bound to an @link AnonymousSyncService.
+ * @return true if this sync uses an account/authority pair, or false if this sync is bound to
+ * a Sync Service.
*/
public boolean hasAuthority() {
return mIsAuthority;
@@ -85,31 +88,30 @@ public class SyncRequest implements Parcelable {
/**
* {@hide}
- * Throws a runtime IllegalArgumentException if this function is called for an
- * anonymous sync.
- *
- * @return (Account, Provider) for this SyncRequest.
+ * @return account object for this sync.
+ * @throws IllegalArgumentException if this function is called for a request that does not
+ * specify an account/provider authority.
*/
- public Pair<Account, String> getProviderInfo() {
+ public Account getAccount() {
if (!hasAuthority()) {
- throw new IllegalArgumentException("Cannot getProviderInfo() for an anonymous sync.");
+ throw new IllegalArgumentException("Cannot getAccount() for a sync that does not"
+ + "specify an authority.");
}
- return Pair.create(mAccountToSync, mAuthority);
+ return mAccountToSync;
}
/**
* {@hide}
- * Throws a runtime IllegalArgumentException if this function is called for a
- * SyncRequest that is bound to an account/provider.
- *
- * @return ComponentName for the service that this sync will bind to.
+ * @return provider for this sync.
+ * @throws IllegalArgumentException if this function is called for a request that does not
+ * specify an account/provider authority.
*/
- public ComponentName getService() {
- if (hasAuthority()) {
- throw new IllegalArgumentException(
- "Cannot getAnonymousService() for a sync that has specified a provider.");
+ public String getProvider() {
+ if (!hasAuthority()) {
+ throw new IllegalArgumentException("Cannot getProvider() for a sync that does not"
+ + "specify a provider.");
}
- return mComponentInfo;
+ return mAuthority;
}
/**
@@ -127,6 +129,7 @@ public class SyncRequest implements Parcelable {
public long getSyncFlexTime() {
return mSyncFlexTimeSecs;
}
+
/**
* {@hide}
* @return the last point in time at which this sync must scheduled.
@@ -159,7 +162,7 @@ public class SyncRequest implements Parcelable {
parcel.writeLong(mSyncFlexTimeSecs);
parcel.writeLong(mSyncRunTimeSecs);
parcel.writeInt((mIsPeriodic ? 1 : 0));
- parcel.writeInt((mAllowMetered ? 1 : 0));
+ parcel.writeInt((mDisallowMetered ? 1 : 0));
parcel.writeLong(mTxBytes);
parcel.writeLong(mRxBytes);
parcel.writeInt((mIsAuthority ? 1 : 0));
@@ -177,7 +180,7 @@ public class SyncRequest implements Parcelable {
mSyncFlexTimeSecs = in.readLong();
mSyncRunTimeSecs = in.readLong();
mIsPeriodic = (in.readInt() != 0);
- mAllowMetered = (in.readInt() != 0);
+ mDisallowMetered = (in.readInt() != 0);
mTxBytes = in.readLong();
mRxBytes = in.readLong();
mIsAuthority = (in.readInt() != 0);
@@ -207,13 +210,13 @@ public class SyncRequest implements Parcelable {
// For now we merge the sync config extras & the custom extras into one bundle.
// TODO: pass the configuration extras through separately.
mExtras.putAll(b.mSyncConfigExtras);
- mAllowMetered = b.mAllowMetered;
+ mDisallowMetered = b.mDisallowMetered;
mTxBytes = b.mTxBytes;
mRxBytes = b.mRxBytes;
}
/**
- * Builder class for a @link SyncRequest. As you build your SyncRequest this class will also
+ * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
* perform validation.
*/
public static class Builder {
@@ -229,12 +232,9 @@ public class SyncRequest implements Parcelable {
private static final int SYNC_TARGET_SERVICE = 1;
/** Specify that this is a sync with a provider. */
private static final int SYNC_TARGET_ADAPTER = 2;
- /**
- * Earliest point of displacement into the future at which this sync can
- * occur.
- */
+ /** Earliest point of displacement into the future at which this sync can occur. */
private long mSyncFlexTimeSecs;
- /** Displacement into the future at which this sync must occur. */
+ /** Latest point of displacement into the future at which this sync must occur. */
private long mSyncRunTimeSecs;
/**
* Sync configuration information - custom user data explicitly provided by the developer.
@@ -253,7 +253,7 @@ public class SyncRequest implements Parcelable {
/** Expected download transfer in bytes. */
private long mRxBytes = -1L;
/** Whether or not this sync can occur on metered networks. Default false. */
- private boolean mAllowMetered;
+ private boolean mDisallowMetered;
/** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */
private int mPriority = 0;
/**
@@ -283,9 +283,8 @@ public class SyncRequest implements Parcelable {
private boolean mExpedited;
/**
- * The {@link SyncService} component that
- * contains the sync logic if this is a provider-less sync, otherwise
- * null.
+ * The sync component that contains the sync logic if this is a provider-less sync,
+ * otherwise null.
*/
private ComponentName mComponentName;
/**
@@ -303,46 +302,28 @@ public class SyncRequest implements Parcelable {
}
/**
- * Developer can define timing constraints for this one-shot request.
- * These values are elapsed real-time.
- *
- * @param whenSeconds The time in seconds at which you want this
- * sync to occur.
- * @param beforeSeconds The amount of time in advance of whenSeconds that this
- * sync may be permitted to occur. This is rounded up to a minimum of 5
- * seconds, for any sync for which whenSeconds > 5.
+ * Request that a sync occur immediately.
*
* Example
* <pre>
- * Perform an immediate sync.
- * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce(0, 0);
- * That is, a sync 0 seconds from now with 0 seconds of flex.
- *
- * Perform a sync in exactly 5 minutes.
- * SyncRequest.Builder builder =
- * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 0);
- *
- * Perform a sync in 5 minutes, with one minute of leeway (between 4 and 5 minutes from
- * now).
- * SyncRequest.Builder builder =
- * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 1 * MIN_IN_SECS);
+ * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
* </pre>
*/
- public Builder syncOnce(long whenSeconds, long beforeSeconds) {
+ public Builder syncOnce() {
if (mSyncType != SYNC_TYPE_UNKNOWN) {
throw new IllegalArgumentException("Sync type has already been defined.");
}
mSyncType = SYNC_TYPE_ONCE;
- setupInterval(whenSeconds, beforeSeconds);
+ setupInterval(0, 0);
return this;
}
/**
* Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
- * Syncs are identified by target {@link SyncService}/{@link android.provider} and by the
- * contents of the extras bundle.
- * You cannot reuse the same builder for one-time syncs after having specified a periodic
- * sync (by calling this function). If you do, an <code>IllegalArgumentException</code>
+ * Syncs are identified by target {@link android.provider}/{@link android.accounts.Account}
+ * and by the contents of the extras bundle.
+ * You cannot reuse the same builder for one-time syncs (by calling this function) after
+ * having specified a periodic sync. If you do, an <code>IllegalArgumentException</code>
* will be thrown.
*
* Example usage.
@@ -394,6 +375,7 @@ public class SyncRequest implements Parcelable {
}
/**
+ * {@hide}
* Developer can provide insight into their payload size; optional. -1 specifies unknown,
* so that you are not restricted to defining both fields.
*
@@ -407,21 +389,20 @@ public class SyncRequest implements Parcelable {
}
/**
- * @param allow false to allow this transfer on metered networks. Default true.
+ * @see android.net.ConnectivityManager#isActiveNetworkMetered()
+ * @param disallow true to enforce that this transfer not occur on metered networks.
+ * Default false.
*/
- public Builder setAllowMetered(boolean allow) {
- mAllowMetered = true;
+ public Builder setDisallowMetered(boolean disallow) {
+ mDisallowMetered = disallow;
return this;
}
/**
- * Specify an authority and account for this transfer. Cannot be used with
- * {@link #setSyncAdapter(ComponentName cname)}.
+ * Specify an authority and account for this transfer.
*
- * @param authority
- * @param account Account to sync. Can be null unless this is a periodic
- * sync, for which verification by the ContentResolver will
- * fail. If a sync is performed without an account, the
+ * @param authority String identifying which content provider to sync.
+ * @param account Account to sync. Can be null unless this is a periodic sync.
*/
public Builder setSyncAdapter(Account account, String authority) {
if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
@@ -435,26 +416,10 @@ public class SyncRequest implements Parcelable {
}
/**
- * Specify the {@link SyncService} component for this sync. This is not validated until
- * sync time so providing an incorrect component name here will not fail. Cannot be used
- * with {@link #setSyncAdapter(Account account, String authority)}.
- *
- * @param cname ComponentName to identify your Anonymous service
- */
- public Builder setSyncAdapter(ComponentName cname) {
- if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
- throw new IllegalArgumentException("Sync target has already been defined.");
- }
- mSyncTarget = SYNC_TARGET_SERVICE;
- mComponentName = cname;
- mAccount = null;
- mAuthority = null;
- return this;
- }
-
- /**
- * Developer-provided extras handed back when sync actually occurs. This bundle is copied
- * into the SyncRequest returned by {@link #build()}.
+ * Optional developer-provided extras handed back in
+ * {@link AbstractThreadedSyncAdapter#onPerformSync(Account, Bundle, String,
+ * ContentProviderClient, SyncResult)} occurs. This bundle is copied into the SyncRequest
+ * returned by {@link #build()}.
*
* Example:
* <pre>
@@ -468,7 +433,7 @@ public class SyncRequest implements Parcelable {
* Bundle extras = new Bundle();
* extras.setString("data", syncData);
* builder.setExtras(extras);
- * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync.
+ * ContentResolver.sync(builder.build()); // Each sync() request is for a unique sync.
* }
* </pre>
* Only values of the following types may be used in the extras bundle:
@@ -509,7 +474,8 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}.
*
- * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * A sync can specify that system sync settings be ignored (user has turned sync off). Not
+ * valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
* {@link #build()}.
*
* @param ignoreSettings true to ignore the sync automatically settings. Default false.
@@ -522,13 +488,13 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}.
*
- * Ignoring back-off will force the sync scheduling process to ignore any back-off that was
- * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil}
- * value that may have been set by the adapter. Successive failures will not honor this
- * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code>
- * in {@link #build()}.
+ * Force the sync scheduling process to ignore any back-off that was the result of a failed
+ * sync, as well as to invalidate any {@link SyncResult#delayUntil} value that may have
+ * been set by the adapter. Successive failures will not honor this flag. Not valid for
+ * periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * {@link #build()}.
*
- * @param ignoreBackoff ignore back off settings. Default false.
+ * @param ignoreBackoff ignore back-off settings. Default false.
*/
public Builder setIgnoreBackoff(boolean ignoreBackoff) {
mIgnoreBackoff = ignoreBackoff;
@@ -538,8 +504,9 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}.
*
- * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
- * {@link #build()}.
+ * A manual sync is functionally equivalent to calling {@link #setIgnoreBackoff(boolean)}
+ * and {@link #setIgnoreSettings(boolean)}. Not valid for periodic sync and will throw an
+ * <code>IllegalArgumentException</code> in {@link #build()}.
*
* @param isManual User-initiated sync or not. Default false.
*/
@@ -549,7 +516,7 @@ public class SyncRequest implements Parcelable {
}
/**
- * An expedited sync runs immediately and can preempt other non-expedited running syncs.
+ * An expedited sync runs immediately and will preempt another non-expedited running sync.
*
* Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
* {@link #build()}.
@@ -562,6 +529,7 @@ public class SyncRequest implements Parcelable {
}
/**
+ * {@hide}
* @param priority the priority of this request among all requests from the calling app.
* Range of [-2,2] similar to how this is done with notifications.
*/
@@ -581,18 +549,18 @@ public class SyncRequest implements Parcelable {
* builder.
*/
public SyncRequest build() {
- // Validate the extras bundle
- ContentResolver.validateSyncExtrasBundle(mCustomExtras);
if (mCustomExtras == null) {
mCustomExtras = new Bundle();
}
+ // Validate the extras bundle
+ ContentResolver.validateSyncExtrasBundle(mCustomExtras);
// Combine builder extra flags into the config bundle.
mSyncConfigExtras = new Bundle();
if (mIgnoreBackoff) {
mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
}
- if (mAllowMetered) {
- mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, true);
+ if (mDisallowMetered) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true);
}
if (mIgnoreSettings) {
mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
@@ -613,13 +581,22 @@ public class SyncRequest implements Parcelable {
// If this is a periodic sync ensure than invalid extras were not set.
validatePeriodicExtras(mCustomExtras);
validatePeriodicExtras(mSyncConfigExtras);
+ // Verify that account and provider are not null.
+ if (mAccount == null) {
+ throw new IllegalArgumentException("Account must not be null for periodic"
+ + " sync.");
+ }
+ if (mAuthority == null) {
+ throw new IllegalArgumentException("Authority must not be null for periodic"
+ + " sync.");
+ }
} else if (mSyncType == SYNC_TYPE_UNKNOWN) {
throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()");
}
// Ensure that a target for the sync has been set.
if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
- throw new IllegalArgumentException("Must specify an adapter with one of"
- + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String");
+ throw new IllegalArgumentException("Must specify an adapter with "
+ + "setSyncAdapter(Account, String");
}
return new SyncRequest(this);
}
diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java
deleted file mode 100644
index 100fd40..0000000
--- a/core/java/android/content/SyncService.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.content;
-
-import android.app.Service;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.Trace;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.HashMap;
-
-/**
- * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
- * behaviour into a service to which the system can bind when requesting an
- * anonymous (providerless/accountless) sync.
- * <p>
- * In order to perform an anonymous sync operation you must extend this service,
- * implementing the abstract methods. This service must then be declared in the
- * application's manifest as usual. You can use this service for other work, however you
- * <b> must not </b> override the onBind() method unless you know what you're doing,
- * which limits the usefulness of this service for other work.
- *
- * <pre>
- * &lt;service ndroid:name=".MyAnonymousSyncService" android:permission="android.permission.SYNC" /&gt;
- * </pre>
- * Like @link android.content.AbstractThreadedSyncAdapter this service supports
- * multiple syncs at the same time. Each incoming startSync() with a unique tag
- * will spawn a thread to do the work of that sync. If startSync() is called
- * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
- * Remember that your service will spawn multiple threads if you schedule multiple syncs
- * at once, so if you mutate local objects you must ensure synchronization.
- */
-public abstract class SyncService extends Service {
-
- /** SyncAdapter Instantiation that any anonymous syncs call. */
- private final AnonymousSyncAdapterImpl mSyncAdapter = new AnonymousSyncAdapterImpl();
-
- /** Keep track of on-going syncs, keyed by tag. */
- @GuardedBy("mLock")
- private final HashMap<Bundle, AnonymousSyncThread>
- mSyncThreads = new HashMap<Bundle, AnonymousSyncThread>();
- /** Lock object for accessing the SyncThreads HashMap. */
- private final Object mSyncThreadLock = new Object();
-
- @Override
- public IBinder onBind(Intent intent) {
- return mSyncAdapter.asBinder();
- }
-
- /** {@hide} */
- private class AnonymousSyncAdapterImpl extends IAnonymousSyncAdapter.Stub {
-
- @Override
- public void startSync(ISyncContext syncContext, Bundle extras) {
- // Wrap the provided Sync Context because it may go away by the time
- // we call it.
- final SyncContext syncContextClient = new SyncContext(syncContext);
- boolean alreadyInProgress = false;
- synchronized (mSyncThreadLock) {
- if (mSyncThreads.containsKey(extras)) {
- // Don't want to call back to SyncManager while still
- // holding lock.
- alreadyInProgress = true;
- } else {
- AnonymousSyncThread syncThread = new AnonymousSyncThread(
- syncContextClient, extras);
- mSyncThreads.put(extras, syncThread);
- syncThread.start();
- }
- }
- if (alreadyInProgress) {
- syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
- }
- }
-
- /**
- * Used by the SM to cancel a specific sync using the {@link
- * com.android.server.content.SyncManager.ActiveSyncContext} as a handle.
- */
- @Override
- public void cancelSync(ISyncContext syncContext) {
- AnonymousSyncThread runningSync = null;
- synchronized (mSyncThreadLock) {
- for (AnonymousSyncThread thread : mSyncThreads.values()) {
- if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
- runningSync = thread;
- break;
- }
- }
- }
- if (runningSync != null) {
- runningSync.interrupt();
- }
- }
- }
-
- /**
- * {@hide}
- * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
- * the ATSA considers an already in-progress sync to be if the account provided is currently
- * syncing, this anonymous sync has no notion of account and therefore considers a sync unique
- * if the provided bundle is different.
- */
- private class AnonymousSyncThread extends Thread {
- private final SyncContext mSyncContext;
- private final Bundle mExtras;
-
- public AnonymousSyncThread(SyncContext syncContext, Bundle extras) {
- mSyncContext = syncContext;
- mExtras = extras;
- }
-
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
- Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());
-
- SyncResult syncResult = new SyncResult();
- try {
- if (isCancelled()) {
- return;
- }
- // Run the sync based off of the provided code.
- SyncService.this.onPerformSync(mExtras, syncResult);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
- if (!isCancelled()) {
- mSyncContext.onFinished(syncResult);
- }
- // Synchronize so that the assignment will be seen by other
- // threads
- // that also synchronize accesses to mSyncThreads.
- synchronized (mSyncThreadLock) {
- mSyncThreads.remove(mExtras);
- }
- }
- }
-
- private boolean isCancelled() {
- return Thread.currentThread().isInterrupted();
- }
- }
-
- /**
- * Initiate an anonymous sync using this service. SyncAdapter-specific
- * parameters may be specified in extras, which is guaranteed to not be
- * null.
- */
- public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
-
-}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index eba69b6..2b0c896 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -216,6 +216,12 @@ interface IPackageManager {
void resetPreferredActivities(int userId);
+ ResolveInfo getLastChosenActivity(in Intent intent,
+ String resolvedType, int flags);
+
+ void setLastChosenActivity(in Intent intent, String resolvedType, int flags,
+ in IntentFilter filter, int match, in ComponentName activity);
+
void addPreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity, int userId);
@@ -226,7 +232,7 @@ interface IPackageManager {
int getPreferredActivities(out List<IntentFilter> outFilters,
out List<ComponentName> outActivities, String packageName);
-
+
/**
* As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}.
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 81f860e..d58b14c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -957,12 +957,24 @@ public abstract class PackageManager {
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports host-
* based NFC card emulation.
+ *
+ * TODO remove when depending apps have moved to new constant.
+ * @hide
+ * @deprecated
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_NFC_HCE = "android.hardware.nfc.hce";
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports host-
+ * based NFC card emulation.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes an accelerometer.
*/
@SdkConstant(SdkConstantType.FEATURE)
diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java
index e0db5e7..ec08393 100644
--- a/core/java/android/ddm/DdmHandleProfiling.java
+++ b/core/java/android/ddm/DdmHandleProfiling.java
@@ -34,6 +34,8 @@ public class DdmHandleProfiling extends ChunkHandler {
public static final int CHUNK_MPSS = type("MPSS");
public static final int CHUNK_MPSE = type("MPSE");
public static final int CHUNK_MPRQ = type("MPRQ");
+ public static final int CHUNK_SPSS = type("SPSS");
+ public static final int CHUNK_SPSE = type("SPSE");
private static DdmHandleProfiling mInstance = new DdmHandleProfiling();
@@ -50,6 +52,8 @@ public class DdmHandleProfiling extends ChunkHandler {
DdmServer.registerHandler(CHUNK_MPSS, mInstance);
DdmServer.registerHandler(CHUNK_MPSE, mInstance);
DdmServer.registerHandler(CHUNK_MPRQ, mInstance);
+ DdmServer.registerHandler(CHUNK_SPSS, mInstance);
+ DdmServer.registerHandler(CHUNK_SPSE, mInstance);
}
/**
@@ -82,6 +86,10 @@ public class DdmHandleProfiling extends ChunkHandler {
return handleMPSE(request);
} else if (type == CHUNK_MPRQ) {
return handleMPRQ(request);
+ } else if (type == CHUNK_SPSS) {
+ return handleSPSS(request);
+ } else if (type == CHUNK_SPSE) {
+ return handleSPSE(request);
} else {
throw new RuntimeException("Unknown packet "
+ ChunkHandler.name(type));
@@ -144,7 +152,7 @@ public class DdmHandleProfiling extends ChunkHandler {
}
try {
- Debug.startMethodTracingDdms(bufferSize, flags);
+ Debug.startMethodTracingDdms(bufferSize, flags, false, 0);
return null; // empty response
} catch (RuntimeException re) {
return createFailChunk(1, re.getMessage());
@@ -178,11 +186,53 @@ public class DdmHandleProfiling extends ChunkHandler {
* Handle a "Method PRofiling Query" request.
*/
private Chunk handleMPRQ(Chunk request) {
- int result = Debug.isMethodTracingActive() ? 1 : 0;
+ int result = Debug.getMethodTracingMode();
/* create a non-empty reply so the handler fires on completion */
byte[] reply = { (byte) result };
return new Chunk(CHUNK_MPRQ, reply, 0, reply.length);
}
+
+ /*
+ * Handle a "Sample Profiling w/Streaming Start" request.
+ */
+ private Chunk handleSPSS(Chunk request) {
+ ByteBuffer in = wrapChunk(request);
+
+ int bufferSize = in.getInt();
+ int flags = in.getInt();
+ int interval = in.getInt();
+ if (false) {
+ Log.v("ddm-heap", "Sample prof stream start: size=" + bufferSize
+ + ", flags=" + flags + ", interval=" + interval);
+ }
+
+ try {
+ Debug.startMethodTracingDdms(bufferSize, flags, true, interval);
+ return null; // empty response
+ } catch (RuntimeException re) {
+ return createFailChunk(1, re.getMessage());
+ }
+ }
+
+ /*
+ * Handle a "Sample Profiling w/Streaming End" request.
+ */
+ private Chunk handleSPSE(Chunk request) {
+ if (false) {
+ Log.v("ddm-heap", "Sample prof stream end");
+ }
+
+ try {
+ Debug.stopMethodTracing();
+ } catch (RuntimeException re) {
+ Log.w("ddm-heap", "Sample prof stream end failed: "
+ + re.getMessage());
+ return createFailChunk(1, re.getMessage());
+ }
+
+ /* VM sent the (perhaps very large) response directly */
+ return null;
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 848d7bc..a4a56d7 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -303,7 +303,8 @@ public interface CameraDevice extends AutoCloseable {
* preview or other continuous stream of frames, without having to submit
* requests through {@link #capture} at video rates.</p>
*
- * <p>To stop the repeating capture, call {@link #stopRepeating}</p>
+ * <p>To stop the repeating capture, call {@link #stopRepeating}. Calling
+ * {@link #flush} will also clear the request.</p>
*
* <p>Calling repeat will replace a burst set up by {@link
* #setRepeatingBurst}, although any in-progress burst will be
@@ -323,6 +324,8 @@ public interface CameraDevice extends AutoCloseable {
* @see #capture
* @see #captureBurst
* @see #setRepeatingBurst
+ * @see #stopRepeating
+ * @see #flush
*/
public void setRepeatingRequest(CaptureRequest request, CaptureListener listener)
throws CameraAccessException;
@@ -348,7 +351,8 @@ public interface CameraDevice extends AutoCloseable {
* requests through {@link #capture} at video rates.</p>
*
* <p>To stop the repeating capture, call {@link #stopRepeating}. Any
- * ongoing burst will still be completed, however.</p>
+ * ongoing burst will still be completed, however. Calling
+ * {@link #flush} will also clear the request.</p>
*
* <p>Calling repeatBurst will replace a repeating request set up by
* {@link #setRepeatingRequest}, although any in-progress capture will be completed
@@ -367,6 +371,8 @@ public interface CameraDevice extends AutoCloseable {
* @see #capture
* @see #captureBurst
* @see #setRepeatingRequest
+ * @see #stopRepeating
+ * @see #flush
*/
public void setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener)
throws CameraAccessException;
@@ -435,6 +441,38 @@ public interface CameraDevice extends AutoCloseable {
public void setErrorListener(ErrorListener listener);
/**
+ * Flush all captures currently pending and in-progress as fast as
+ * possible.
+ *
+ * <p>The camera device will discard all of its current work as fast as
+ * possible. Some in-flight captures may complete successfully and call
+ * {@link CaptureListener#onCaptureComplete}, while others will trigger
+ * their {@link CaptureListener#onCaptureFailed} callbacks. If a repeating
+ * request or a repeating burst is set, it will be cleared by the flush.</p>
+ *
+ * <p>This method is the fastest way to idle the camera device for
+ * reconfiguration with {@link #configureOutputs}, at the cost of discarding
+ * in-progress work. Once the flush is complete, the idle callback will be
+ * called.</p>
+ *
+ * <p>Flushing will introduce at least a brief pause in the stream of data
+ * from the camera device, since once the flush is complete, the first new
+ * request has to make it through the entire camera pipeline before new
+ * output buffers are produced.</p>
+ *
+ * <p>This means that using {@code flush()} to simply remove pending
+ * requests is not recommended; it's best used for quickly switching output
+ * configurations, or for cancelling long in-progress requests (such as a
+ * multi-second capture).</p>
+ *
+ * @throws CameraAccessException if the camera device is no longer connected
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ * @see #configureOutputs
+ */
+ public void flush() throws CameraAccessException;
+
+ /**
* Close the connection to this camera device. After this call, all calls to
* the camera device interface will throw a {@link IllegalStateException},
* except for calls to close().
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index 5a9b72f..b1724de 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -45,4 +45,6 @@ interface ICameraDeviceUser
int getCameraInfo(out CameraMetadata info);
int waitUntilIdle();
+
+ int flush();
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index e7495d3..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;
}
}
}
@@ -242,6 +280,20 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
@Override
+ public void flush() throws CameraAccessException {
+ synchronized (mLock) {
+ try {
+ mRemoteDevice.flush();
+ } catch (CameraRuntimeException e) {
+ throw e.asChecked();
+ } catch (RemoteException e) {
+ // impossible
+ return;
+ }
+ }
+ }
+
+ @Override
public void close() throws Exception {
// TODO: every method should throw IllegalStateException after close has been called
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
index 74c2c59..01977cd 100644
--- a/core/java/android/net/CaptivePortalTracker.java
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -16,22 +16,16 @@
package android.net;
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
-import android.os.UserHandle;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -46,7 +40,6 @@ import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -60,8 +53,6 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
-import com.android.internal.R;
-
/**
* This class allows captive portal detection on a network.
* @hide
@@ -71,7 +62,6 @@ public class CaptivePortalTracker extends StateMachine {
private static final String TAG = "CaptivePortalTracker";
private static final String DEFAULT_SERVER = "clients3.google.com";
- private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
private static final int SOCKET_TIMEOUT_MS = 10000;
@@ -93,7 +83,6 @@ public class CaptivePortalTracker extends StateMachine {
private String mServer;
private String mUrl;
- private boolean mNotificationShown = false;
private boolean mIsCaptivePortalCheckEnabled = false;
private IConnectivityManager mConnService;
private TelephonyManager mTelephonyManager;
@@ -192,12 +181,12 @@ public class CaptivePortalTracker extends StateMachine {
private class DefaultState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
+ setNotificationOff();
}
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString() + "\n");
+ if (DBG) log(getName() + message.toString());
switch (message.what) {
case CMD_DETECT_PORTAL:
NetworkInfo info = (NetworkInfo) message.obj;
@@ -219,23 +208,24 @@ public class CaptivePortalTracker extends StateMachine {
private class NoActiveNetworkState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
mNetworkInfo = null;
- /* Clear any previous notification */
- setNotificationVisible(false);
}
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString() + "\n");
+ if (DBG) log(getName() + message.toString());
InetAddress server;
NetworkInfo info;
switch (message.what) {
case CMD_CONNECTIVITY_CHANGE:
info = (NetworkInfo) message.obj;
- if (info.isConnected() && isActiveNetwork(info)) {
- mNetworkInfo = info;
- transitionTo(mDelayedCaptiveCheckState);
+ if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+ if (info.isConnected() && isActiveNetwork(info)) {
+ mNetworkInfo = info;
+ transitionTo(mDelayedCaptiveCheckState);
+ }
+ } else {
+ log(getName() + " not a wifi connectivity change, ignore");
}
break;
default:
@@ -248,7 +238,7 @@ public class CaptivePortalTracker extends StateMachine {
private class ActiveNetworkState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
+ setNotificationOff();
}
@Override
@@ -281,7 +271,6 @@ public class CaptivePortalTracker extends StateMachine {
private class DelayedCaptiveCheckState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
Message message = obtainMessage(CMD_DELAYED_CAPTIVE_CHECK, ++mDelayedCheckToken, 0);
if (mDeviceProvisioned) {
sendMessageDelayed(message, DELAYED_CHECK_INTERVAL_MS);
@@ -292,7 +281,7 @@ public class CaptivePortalTracker extends StateMachine {
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString() + "\n");
+ if (DBG) log(getName() + message.toString());
switch (message.what) {
case CMD_DELAYED_CAPTIVE_CHECK:
if (message.arg1 == mDelayedCheckToken) {
@@ -308,7 +297,12 @@ public class CaptivePortalTracker extends StateMachine {
if (captive) {
// Setup Wizard will assist the user in connecting to a captive
// portal, so make the notification visible unless during setup
- setNotificationVisible(true);
+ try {
+ mConnService.setProvisioningNotificationVisible(true,
+ mNetworkInfo.getType(), mNetworkInfo.getExtraInfo(), mUrl);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
}
} else {
Intent intent = new Intent(
@@ -366,6 +360,15 @@ public class CaptivePortalTracker extends StateMachine {
return false;
}
+ private void setNotificationOff() {
+ try {
+ mConnService.setProvisioningNotificationVisible(false, ConnectivityManager.TYPE_NONE,
+ null, null);
+ } catch (RemoteException e) {
+ log("setNotificationOff: " + e);
+ }
+ }
+
/**
* Do a URL fetch on a known server to see if we get the data we expect.
* Measure the response time and broadcast that.
@@ -394,17 +397,14 @@ public class CaptivePortalTracker extends StateMachine {
long responseTimestamp = SystemClock.elapsedRealtime();
// we got a valid response, but not from the real google
- boolean isCaptivePortal = urlConnection.getResponseCode() != 204;
+ int rspCode = urlConnection.getResponseCode();
+ boolean isCaptivePortal = rspCode != 204;
sendNetworkConditionsBroadcast(true /* response received */, isCaptivePortal,
requestTimestamp, responseTimestamp);
+
+ if (DBG) log("isCaptivePortal: ret=" + isCaptivePortal + " rspCode=" + rspCode);
return isCaptivePortal;
- } catch (SocketTimeoutException e) {
- if (DBG) log("Probably a portal: exception " + e);
- if (requestTimestamp != -1) {
- sendFailedCaptivePortalCheckBroadcast(requestTimestamp);
- } // else something went wrong with setting up the urlConnection
- return true;
} catch (IOException e) {
if (DBG) log("Probably not a portal: exception " + e);
if (requestTimestamp != -1) {
@@ -435,77 +435,6 @@ public class CaptivePortalTracker extends StateMachine {
return null;
}
- private void setNotificationVisible(boolean visible) {
- // if it should be hidden and it is already hidden, then noop
- if (!visible && !mNotificationShown) {
- if (DBG) log("setNotivicationVisible: false and not shown, so noop");
- return;
- }
-
- Resources r = Resources.getSystem();
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (visible) {
- CharSequence title;
- CharSequence details;
- int icon;
- String url = null;
- switch (mNetworkInfo.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- title = r.getString(R.string.wifi_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- mNetworkInfo.getExtraInfo());
- icon = R.drawable.stat_notify_wifi_in_range;
- url = mUrl;
- break;
- case ConnectivityManager.TYPE_MOBILE:
- title = r.getString(R.string.network_available_sign_in, 0);
- // TODO: Change this to pull from NetworkInfo once a printable
- // name has been added to it
- details = mTelephonyManager.getNetworkOperatorName();
- icon = R.drawable.stat_notify_rssi_in_range;
- try {
- url = mConnService.getMobileProvisioningUrl();
- if (TextUtils.isEmpty(url)) {
- url = mConnService.getMobileRedirectedProvisioningUrl();
- }
- } catch(RemoteException e) {
- e.printStackTrace();
- }
- if (TextUtils.isEmpty(url)) {
- url = mUrl;
- }
- break;
- default:
- title = r.getString(R.string.network_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- mNetworkInfo.getExtraInfo());
- icon = R.drawable.stat_notify_rssi_in_range;
- url = mUrl;
- break;
- }
-
- Notification notification = new Notification();
- notification.when = 0;
- notification.icon = icon;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
- Intent.FLAG_ACTIVITY_NEW_TASK);
- notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
- notification.tickerText = title;
- notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
-
- if (DBG) log("setNotivicationVisible: make visible");
- notificationManager.notify(NOTIFICATION_ID, 1, notification);
- } else {
- if (DBG) log("setNotivicationVisible: cancel notification");
- notificationManager.cancel(NOTIFICATION_ID, 1);
- }
- mNotificationShown = visible;
- }
-
private void sendFailedCaptivePortalCheckBroadcast(long requestTimestampMs) {
sendNetworkConditionsBroadcast(false /* response received */, false /* ignored */,
requestTimestampMs, 0 /* ignored */);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index f6a3a4a..3874369 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -624,6 +624,29 @@ public class ConnectivityManager {
}
/**
+ * Returns details about the Provisioning or currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no network default network is currently active
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * {@hide}
+ */
+ public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+ try {
+ return mService.getProvisioningOrActiveNetworkInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Returns the IP information for the current default network.
*
* @return a {@link LinkProperties} object describing the IP info
@@ -1357,63 +1380,19 @@ public class ConnectivityManager {
}
/**
- * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE)
- */
-
- /**
- * No connection was possible to the network.
- * {@hide}
- */
- public static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
-
- /**
- * A connection was made to the internet, all is well.
- * {@hide}
- */
- public static final int CMP_RESULT_CODE_CONNECTABLE = 1;
-
- /**
- * A connection was made but there was a redirection, we appear to be in walled garden.
- * This is an indication of a warm sim on a mobile network.
- * {@hide}
- */
- public static final int CMP_RESULT_CODE_REDIRECTED = 2;
-
- /**
- * A connection was made but no dns server was available to resolve a name to address.
- * This is an indication of a warm sim on a mobile network.
+ * Check mobile provisioning.
*
- * {@hide}
- */
- public static final int CMP_RESULT_CODE_NO_DNS = 3;
-
- /**
- * A connection was made but could not open a TCP connection.
- * This is an indication of a warm sim on a mobile network.
- * {@hide}
- */
- public static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 4;
-
- /**
- * Check mobile provisioning. The resultCode passed to
- * onReceiveResult will be one of the CMP_RESULT_CODE_xxxx values above.
- * This may take a minute or more to complete.
- *
- * @param sendNotificaiton, when true a notification will be sent to user.
* @param suggestedTimeOutMs, timeout in milliseconds
- * @param resultReceiver needs to be supplied to receive the result
*
* @return time out that will be used, maybe less that suggestedTimeOutMs
* -1 if an error.
*
* {@hide}
*/
- public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs,
- ResultReceiver resultReceiver) {
+ public int checkMobileProvisioning(int suggestedTimeOutMs) {
int timeOutMs = -1;
try {
- timeOutMs = mService.checkMobileProvisioning(sendNotification, suggestedTimeOutMs,
- resultReceiver);
+ timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
} catch (RemoteException e) {
}
return timeOutMs;
@@ -1481,4 +1460,20 @@ public class ConnectivityManager {
return null;
}
}
+
+ /**
+ * Set sign in error notification to visible or in visible
+ *
+ * @param visible
+ * @param networkType
+ *
+ * {@hide}
+ */
+ public void setProvisioningNotificationVisible(boolean visible, int networkType,
+ String extraInfo, String url) {
+ try {
+ mService.setProvisioningNotificationVisible(visible, networkType, extraInfo, url);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index bf2dade..c07e900 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -50,6 +50,8 @@ interface IConnectivityManager
NetworkInfo getNetworkInfo(int networkType);
NetworkInfo[] getAllNetworkInfo();
+ NetworkInfo getProvisioningOrActiveNetworkInfo();
+
boolean isNetworkSupported(int networkType);
LinkProperties getActiveLinkProperties();
@@ -141,7 +143,7 @@ interface IConnectivityManager
int findConnectionTypeForIface(in String iface);
- int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, in ResultReceiver resultReceiver);
+ int checkMobileProvisioning(int suggestedTimeOutMs);
String getMobileProvisioningUrl();
@@ -153,4 +155,5 @@ interface IConnectivityManager
LinkInfo[] getAllLinkInfo();
+ void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo, in String url);
}
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/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index faa13b0..125d5c1 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -61,8 +61,12 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
private ITelephony mPhoneService;
private String mApnType;
+ private NetworkInfo mNetworkInfo;
private boolean mTeardownRequested = false;
private Handler mTarget;
+ private Context mContext;
+ private LinkProperties mLinkProperties;
+ private LinkCapabilities mLinkCapabilities;
private boolean mPrivateDnsRouteSet = false;
private boolean mDefaultRouteSet = false;
@@ -106,6 +110,7 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN);
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
mContext.registerReceiver(new MobileDataStateReceiver(), filter);
@@ -184,10 +189,41 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
public void releaseWakeLock() {
}
+ private void updateLinkProperitesAndCapatilities(Intent intent) {
+ mLinkProperties = intent.getParcelableExtra(
+ PhoneConstants.DATA_LINK_PROPERTIES_KEY);
+ if (mLinkProperties == null) {
+ loge("CONNECTED event did not supply link properties.");
+ mLinkProperties = new LinkProperties();
+ }
+ mLinkCapabilities = intent.getParcelableExtra(
+ PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
+ if (mLinkCapabilities == null) {
+ loge("CONNECTED event did not supply link capabilities.");
+ mLinkCapabilities = new LinkCapabilities();
+ }
+ }
+
private class MobileDataStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TelephonyIntents.
+ ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN)) {
+ String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
+ String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
+ if (!TextUtils.equals(mApnType, apnType)) {
+ return;
+ }
+ if (DBG) {
+ log("Broadcast received: " + intent.getAction() + " apnType=" + apnType
+ + " apnName=" + apnName);
+ }
+
+ // Make us in the connecting state until we make a new TYPE_MOBILE_PROVISIONING
+ mMobileDataState = PhoneConstants.DataState.CONNECTING;
+ updateLinkProperitesAndCapatilities(intent);
+ setDetailedState(DetailedState.CONNECTED_TO_PROVISIONING_NETWORK, "", apnName);
+ } else if (intent.getAction().equals(TelephonyIntents.
ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
if (VDBG) {
@@ -249,18 +285,7 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
setDetailedState(DetailedState.SUSPENDED, reason, apnName);
break;
case CONNECTED:
- mLinkProperties = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_PROPERTIES_KEY);
- if (mLinkProperties == null) {
- loge("CONNECTED event did not supply link properties.");
- mLinkProperties = new LinkProperties();
- }
- mLinkCapabilities = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
- if (mLinkCapabilities == null) {
- loge("CONNECTED event did not supply link capabilities.");
- mLinkCapabilities = new LinkCapabilities();
- }
+ updateLinkProperitesAndCapatilities(intent);
setDetailedState(DetailedState.CONNECTED, reason, apnName);
break;
}
@@ -319,8 +344,8 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
if (DBG) {
- log("Received " + intent.getAction() +
- " broadcast" + (reason == null ? "" : "(" + reason + ")"));
+ log("Broadcast received: " + intent.getAction() +
+ " reason=" + reason == null ? "null" : reason);
}
setDetailedState(DetailedState.FAILED, reason, apnName);
} else {
@@ -412,6 +437,13 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
}
+ /**
+ * @return true if this is ready to operate
+ */
+ public boolean isReady() {
+ return mDataConnectionTrackerAc != null;
+ }
+
@Override
public void captivePortalCheckComplete() {
// not implemented
@@ -574,6 +606,40 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
}
}
+ /**
+ * Inform DCT mobile provisioning has started, it ends when provisioning completes.
+ */
+ public void enableMobileProvisioning(String url) {
+ if (DBG) log("enableMobileProvisioning(url=" + url + ")");
+ final AsyncChannel channel = mDataConnectionTrackerAc;
+ if (channel != null) {
+ Message msg = Message.obtain();
+ msg.what = DctConstants.CMD_ENABLE_MOBILE_PROVISIONING;
+ msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, url));
+ channel.sendMessage(msg);
+ }
+ }
+
+ /**
+ * Return if this network is the provisioning network. Valid only if connected.
+ * @param met
+ */
+ public boolean isProvisioningNetwork() {
+ boolean retVal;
+ try {
+ Message msg = Message.obtain();
+ msg.what = DctConstants.CMD_IS_PROVISIONING_APN;
+ msg.setData(Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType));
+ Message result = mDataConnectionTrackerAc.sendMessageSynchronously(msg);
+ retVal = result.arg1 == DctConstants.ENABLED;
+ } catch (NullPointerException e) {
+ loge("isProvisioningNetwork: X " + e);
+ retVal = false;
+ }
+ if (DBG) log("isProvisioningNetwork: retVal=" + retVal);
+ return retVal;
+ }
+
@Override
public void addStackedLink(LinkProperties link) {
mLinkProperties.addStackedLink(link);
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 689dae5..dabc73a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -84,6 +84,12 @@ public class NetworkInfo implements Parcelable {
VERIFYING_POOR_LINK,
/** Checking if network is a captive portal */
CAPTIVE_PORTAL_CHECK,
+ /**
+ * Network is connected to provisioning network
+ * TODO: Probably not needed when we add TYPE_PROVISIONING_NETWORK
+ * @hide
+ */
+ CONNECTED_TO_PROVISIONING_NETWORK
}
/**
@@ -108,6 +114,7 @@ public class NetworkInfo implements Parcelable {
stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
+ stateMap.put(DetailedState.CONNECTED_TO_PROVISIONING_NETWORK, State.CONNECTED);
}
private int mNetworkType;
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/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 9c97659..15d0475 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -48,5 +48,6 @@ interface INfcAdapter
void dispatch(in Tag tag);
+ void setReaderMode (IBinder b, int flags);
void setP2pModes(int initatorModes, int targetModes);
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 10183c0..d0d943c 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -19,6 +19,7 @@ package android.nfc;
import android.app.Activity;
import android.app.Application;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
@@ -111,6 +112,9 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
NfcAdapter.CreateBeamUrisCallback uriCallback = null;
Uri[] uris = null;
int flags = 0;
+ int readerModeFlags = 0;
+ Binder token;
+
public NfcActivityState(Activity activity) {
if (activity.getWindow().isDestroyed()) {
throw new IllegalStateException("activity is already destroyed");
@@ -120,6 +124,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
resumed = activity.isResumed();
this.activity = activity;
+ this.token = new Binder();
registerApplication(activity.getApplication());
}
public void destroy() {
@@ -131,6 +136,8 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
onNdefPushCompleteCallback = null;
uriCallback = null;
uris = null;
+ readerModeFlags = 0;
+ token = null;
}
@Override
public String toString() {
@@ -190,6 +197,44 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
mDefaultEvent = new NfcEvent(mAdapter);
}
+ public void enableReaderMode(Activity activity, int flags) {
+ boolean isResumed;
+ Binder token;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.readerModeFlags = flags;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ setReaderMode(token, flags);
+ }
+ }
+
+ public void disableReaderMode(Activity activity) {
+ boolean isResumed;
+ Binder token;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.readerModeFlags = 0;
+ token = state.token;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ setReaderMode(token, 0);
+ }
+
+ }
+
+ public void setReaderMode(Binder token, int flags) {
+ if (DBG) Log.d(TAG, "Setting reader mode");
+ try {
+ NfcAdapter.sService.setReaderMode(token, flags);
+ } catch (RemoteException e) {
+ mAdapter.attemptDeadServiceRecovery(e);
+ }
+ }
+
public void setNdefPushContentUri(Activity activity, Uri[] uris) {
boolean isResumed;
synchronized (NfcActivityManager.this) {
@@ -341,11 +386,18 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
/** Callback from Activity life-cycle, on main thread */
@Override
public void onActivityResumed(Activity activity) {
+ int readerModeFlags = 0;
+ Binder token;
synchronized (NfcActivityManager.this) {
NfcActivityState state = findActivityState(activity);
if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
if (state == null) return;
state.resumed = true;
+ token = state.token;
+ readerModeFlags = state.readerModeFlags;
+ }
+ if (readerModeFlags != 0) {
+ setReaderMode(token, readerModeFlags);
}
requestNfcServiceCallback();
}
@@ -353,11 +405,19 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
/** Callback from Activity life-cycle, on main thread */
@Override
public void onActivityPaused(Activity activity) {
+ boolean readerModeFlagsSet;
+ Binder token;
synchronized (NfcActivityManager.this) {
NfcActivityState state = findActivityState(activity);
if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
if (state == null) return;
state.resumed = false;
+ token = state.token;
+ readerModeFlagsSet = state.readerModeFlags != 0;
+ }
+ if (readerModeFlagsSet) {
+ // Restore default p2p modes
+ setReaderMode(token, 0);
}
}
@@ -381,5 +441,4 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
}
}
}
-
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 2a4f93c..fa0c1f6 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -195,6 +195,50 @@ public final class NfcAdapter {
public static final int STATE_ON = 3;
public static final int STATE_TURNING_OFF = 4;
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, int)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-A technology.
+ */
+ public static final int FLAG_READER_NFC_A = 0x1;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, int)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-B technology.
+ */
+ public static final int FLAG_READER_NFC_B = 0x2;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, int)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-F technology.
+ */
+ public static final int FLAG_READER_NFC_F = 0x4;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, int)}.
+ * <p>
+ * Setting this flag enables polling for Nfc-V (ISO15693) technology.
+ */
+ public static final int FLAG_READER_NFC_V = 0x8;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, int)}.
+ * <p>
+ * Setting this flag enables polling for Kovio technology.
+ */
+ public static final int FLAG_READER_KOVIO = 0x10;
+
+ /**
+ * Flag for use with {@link #enableReaderMode(Activity, int)}.
+ * <p>
+ * Setting this flag allows the caller to prevent the
+ * platform from performing an NDEF check on the tags it
+ * finds.
+ */
+ public static final int FLAG_READER_SKIP_NDEF_CHECK = 0x80;
+
/** @hide */
public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
@@ -1112,6 +1156,44 @@ public final class NfcAdapter {
}
/**
+ * Limit the NFC controller to reader mode while this Activity is in the foreground.
+ *
+ * <p>In this mode the NFC controller will only act as an NFC tag reader/writer,
+ * thus disabling any peer-to-peer (Android Beam) and card-emulation modes of
+ * the NFC adapter on this device.
+ *
+ * <p>Use {@link #FLAG_READER_SKIP_NDEF_CHECK} to prevent the platform from
+ * performing any NDEF checks in reader mode. Note that this will prevent the
+ * {@link Ndef} tag technology from being enumerated on the tag, and that
+ * NDEF-based tag dispatch will not be functional.
+ *
+ * <p>It is recommended to combine this method with
+ * {@link #enableForegroundDispatch(Activity, PendingIntent, IntentFilter[], String[][])
+ * to ensure that tags are delivered to this activity.
+ *
+ * <p>For interacting with tags that are emulated on another Android device
+ * using Android's host-based card-emulation, the recommended flags are
+ * {@link #FLAG_READER_NFC_A} and {@link #FLAG_READER_SKIP_NDEF_CHECK}.
+ *
+ * @param activity the Activity that requests the adapter to be in reader mode
+ * @param flags Flags indicating poll technologies and other optional parameters
+ */
+ public void enableReaderMode(Activity activity, int flags) {
+ mNfcActivityManager.enableReaderMode(activity, flags);
+ }
+
+ /**
+ * Restore the NFC adapter to normal mode of operation: supporting
+ * peer-to-peer (Android Beam), card emulation, and polling for
+ * all supported tag technologies.
+ *
+ * @param activity the Activity that currently has reader mode enabled
+ */
+ public void disableReaderMode(Activity activity) {
+ mNfcActivityManager.disableReaderMode(activity);
+ }
+
+ /**
* Enable NDEF message push over NFC while this Activity is in the foreground.
*
* <p>You must explicitly call this method every time the activity is
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 3f7e3ef..41c6603 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -75,16 +75,22 @@ public final class ApduServiceInfo implements Parcelable {
final HashMap<String, AidGroup> mCategoryToGroup;
/**
+ * Whether this service should only be started when the device is unlocked.
+ */
+ final boolean mRequiresDeviceUnlock;
+
+ /**
* @hide
*/
public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
- ArrayList<AidGroup> aidGroups) {
+ ArrayList<AidGroup> aidGroups, boolean requiresUnlock) {
this.mService = info;
this.mDescription = description;
this.mAidGroups = aidGroups;
this.mAids = new ArrayList<String>();
this.mCategoryToGroup = new HashMap<String, AidGroup>();
this.mOnHost = onHost;
+ this.mRequiresDeviceUnlock = requiresUnlock;
for (AidGroup aidGroup : aidGroups) {
this.mCategoryToGroup.put(aidGroup.category, aidGroup);
this.mAids.addAll(aidGroup.aids);
@@ -99,8 +105,12 @@ public final class ApduServiceInfo implements Parcelable {
if (onHost) {
parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
if (parser == null) {
- throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
- " meta-data");
+ Log.d(TAG, "Didn't find service meta-data, trying legacy.");
+ parser = si.loadXmlMetaData(pm, HostApduService.OLD_SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+ " meta-data");
+ }
}
} else {
parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
@@ -132,12 +142,16 @@ public final class ApduServiceInfo implements Parcelable {
mService = info;
mDescription = sa.getString(
com.android.internal.R.styleable.HostApduService_description);
+ mRequiresDeviceUnlock = sa.getBoolean(
+ com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
+ false);
} else {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.OffHostApduService);
mService = info;
mDescription = sa.getString(
com.android.internal.R.styleable.OffHostApduService_description);
+ mRequiresDeviceUnlock = false;
}
mAidGroups = new ArrayList<AidGroup>();
@@ -160,12 +174,12 @@ public final class ApduServiceInfo implements Parcelable {
com.android.internal.R.styleable.AidGroup_description);
String groupCategory = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_category);
- if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) {
- groupCategory = CardEmulationManager.CATEGORY_OTHER;
+ if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
+ groupCategory = CardEmulation.CATEGORY_OTHER;
}
currentGroup = mCategoryToGroup.get(groupCategory);
if (currentGroup != null) {
- if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) {
+ if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
Log.e(TAG, "Not allowing multiple aid-groups in the " +
groupCategory + " category");
currentGroup = null;
@@ -226,6 +240,10 @@ public final class ApduServiceInfo implements Parcelable {
return mOnHost;
}
+ public boolean requiresUnlock() {
+ return mRequiresDeviceUnlock;
+ }
+
public CharSequence loadLabel(PackageManager pm) {
return mService.loadLabel(pm);
}
@@ -287,6 +305,7 @@ public final class ApduServiceInfo implements Parcelable {
if (mAidGroups.size() > 0) {
dest.writeTypedList(mAidGroups);
}
+ dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
};
public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
@@ -301,7 +320,8 @@ public final class ApduServiceInfo implements Parcelable {
if (numGroups > 0) {
source.readTypedList(aidGroups, AidGroup.CREATOR);
}
- return new ApduServiceInfo(info, onHost, description, aidGroups);
+ boolean requiresUnlock = (source.readInt() != 0) ? true : false;
+ return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock);
}
@Override
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
new file mode 100644
index 0000000..3cd7863
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -0,0 +1,343 @@
+/*
+ * 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.nfc.cardemulation;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.ActivityThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+public final class CardEmulation {
+ static final String TAG = "CardEmulation";
+
+ /**
+ * Activity action: ask the user to change the default
+ * card emulation service for a certain category. This will
+ * show a dialog that asks the user whether he wants to
+ * replace the current default service with the service
+ * identified with the ComponentName specified in
+ * {@link #EXTRA_SERVICE_COMPONENT}, for the category
+ * specified in {@link #EXTRA_CATEGORY}
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_DEFAULT =
+ "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+
+ /**
+ * The category extra for {@link #ACTION_CHANGE_DEFAULT}
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_CATEGORY = "category";
+
+ /**
+ * The ComponentName object passed in as a parcelable
+ * extra for {@link #ACTION_CHANGE_DEFAULT}
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_SERVICE_COMPONENT = "component";
+
+ /**
+ * The payment category can be used to indicate that an AID
+ * represents a payment application.
+ */
+ public static final String CATEGORY_PAYMENT = "payment";
+
+ /**
+ * If an AID group does not contain a category, or the
+ * specified category is not defined by the platform version
+ * that is parsing the AID group, all AIDs in the group will
+ * automatically be categorized under the {@link #CATEGORY_OTHER}
+ * category.
+ */
+ public static final String CATEGORY_OTHER = "other";
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user has set a default service for this
+ * AID category. If a remote reader selects any of the AIDs
+ * that the default service has registered in this category,
+ * that service will automatically be bound to to handle
+ * the transaction.
+ *
+ * <p>There are still cases where a service that is
+ * not the default for a category can selected:
+ * <p>
+ * If a remote reader selects an AID in this category
+ * that is not handled by the default service, and there is a set
+ * of other services {S} that do handle this AID, the
+ * user is asked if he wants to use any of the services in
+ * {S} instead.
+ * <p>
+ * As a special case, if the size of {S} is one, containing a single service X,
+ * and all AIDs X has registered in this category are not
+ * registered by any other service, then X will be
+ * selected automatically without asking the user.
+ * <p>Example:
+ * <ul>
+ * <li>Service A registers AIDs "1", "2" and "3" in the category
+ * <li>Service B registers AIDs "3" and "4" in the category
+ * <li>Service C registers AIDs "5" and "6" in the category
+ * </ul>
+ * In this case, the following will happen when service A
+ * is the default:
+ * <ul>
+ * <li>Reader selects AID "1", "2" or "3": service A is invoked automatically
+ * <li>Reader selects AID "4": the user is asked to confirm he
+ * wants to use service B, because its AIDs overlap with service A.
+ * <li>Reader selects AID "5" or "6": service C is invoked automatically,
+ * because all AIDs it has asked for are only registered by C,
+ * and there is no overlap.
+ * </ul>
+ *
+ */
+ public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, whenever an AID of this category is selected,
+ * the user is asked which service he wants to use to handle
+ * the transaction, even if there is only one matching service.
+ */
+ public static final int SELECTION_MODE_ALWAYS_ASK = 1;
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user will only be asked to select a service
+ * if the selected AID has been registered by multiple applications.
+ */
+ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+ static boolean sIsInitialized = false;
+ static HashMap<Context, CardEmulation> sCardEmus = new HashMap();
+ static INfcCardEmulation sService;
+
+ final Context mContext;
+
+ private CardEmulation(Context context, INfcCardEmulation service) {
+ mContext = context.getApplicationContext();
+ sService = service;
+ }
+
+ public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
+ if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+ Context context = adapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null.");
+ throw new UnsupportedOperationException();
+ }
+ if (!sIsInitialized) {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get PackageManager");
+ throw new UnsupportedOperationException();
+ }
+ try {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ Log.e(TAG, "This device does not support card emulation");
+ throw new UnsupportedOperationException();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManager query failed.");
+ throw new UnsupportedOperationException();
+ }
+ sIsInitialized = true;
+ }
+ CardEmulation manager = sCardEmus.get(context);
+ if (manager == null) {
+ // Get card emu service
+ INfcCardEmulation service = adapter.getCardEmulationService();
+ manager = new CardEmulation(context, service);
+ sCardEmus.put(context, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Allows an application to query whether a service is currently
+ * the default service to handle a card emulation category.
+ *
+ * <p>Note that if {@link #getSelectionModeForCategory(String)}
+ * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always
+ * return false.
+ *
+ * @param service The ComponentName of the service
+ * @param category The category
+ * @return whether service is currently the default service for the category.
+ */
+ public boolean isDefaultServiceForCategory(ComponentName service, String category) {
+ try {
+ return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
+ category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ *
+ * Allows an application to query whether a service is currently
+ * the default handler for a specified ISO7816-4 Application ID.
+ *
+ * @param service The ComponentName of the service
+ * @param aid The ISO7816-4 Application ID
+ * @return
+ */
+ public boolean isDefaultServiceForAid(ComponentName service, String aid) {
+ try {
+ return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Returns the application selection mode for the passed in category.
+ * Valid return values are:
+ * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+ * application for this category, which will be preferred.
+ * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+ * every time what app he would like to use in this category.
+ * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+ * to pick a service if there is a conflict.
+ * @param category The category, for example {@link #CATEGORY_PAYMENT}
+ * @return
+ */
+ public int getSelectionModeForCategory(String category) {
+ if (CATEGORY_PAYMENT.equals(category)) {
+ String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+ if (defaultComponent != null) {
+ return SELECTION_MODE_PREFER_DEFAULT;
+ } else {
+ return SELECTION_MODE_ALWAYS_ASK;
+ }
+ } else {
+ // All other categories are in "only ask if conflict" mode
+ return SELECTION_MODE_ASK_IF_CONFLICT;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultServiceForCategory(ComponentName service, String category) {
+ try {
+ return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service,
+ category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultForNextTap(ComponentName service) {
+ try {
+ return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+ /**
+ * @hide
+ */
+ public List<ApduServiceInfo> getServices(String category) {
+ try {
+ return sService.getServices(UserHandle.myUserId(), category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getServices(UserHandle.myUserId(), category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ void recoverService() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ sService = adapter.getCardEmulationService();
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/CardEmulationManager.java b/core/java/android/nfc/cardemulation/CardEmulationManager.java
index 537fded..124ea1c 100644
--- a/core/java/android/nfc/cardemulation/CardEmulationManager.java
+++ b/core/java/android/nfc/cardemulation/CardEmulationManager.java
@@ -27,11 +27,16 @@ import android.nfc.INfcCardEmulation;
import android.nfc.NfcAdapter;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import java.util.HashMap;
import java.util.List;
+/**
+ * TODO Remove when calling .apks are upgraded
+ * @hide
+ */
public final class CardEmulationManager {
static final String TAG = "CardEmulationManager";
@@ -78,19 +83,68 @@ public final class CardEmulationManager {
*/
public static final String CATEGORY_OTHER = "other";
- static boolean sIsInitialized = false;
- static HashMap<Context, CardEmulationManager> sCardEmuManagers = new HashMap();
- static INfcCardEmulation sService;
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user has set a default service for this
+ * AID category. If a remote reader selects any of the AIDs
+ * that the default service has registered in this category,
+ * that service will automatically be bound to to handle
+ * the transaction.
+ *
+ * <p>There are still cases where a service that is
+ * not the default for a category can selected:
+ * <p>
+ * If a remote reader selects an AID in this category
+ * that is not handled by the default service, and there is a set
+ * of other services {S} that do handle this AID, the
+ * user is asked if he wants to use any of the services in
+ * {S} instead.
+ * <p>
+ * As a special case, if the size of {S} is one, containing a single service X,
+ * and all AIDs X has registered in this category are not
+ * registered by any other service, then X will be
+ * selected automatically without asking the user.
+ * <p>Example:
+ * <ul>
+ * <li>Service A registers AIDs "1", "2" and "3" in the category
+ * <li>Service B registers AIDs "3" and "4" in the category
+ * <li>Service C registers AIDs "5" and "6" in the category
+ * </ul>
+ * In this case, the following will happen when service A
+ * is the default:
+ * <ul>
+ * <li>Reader selects AID "1", "2" or "3": service A is invoked automatically
+ * <li>Reader selects AID "4": the user is asked to confirm he
+ * wants to use service B, because its AIDs overlap with service A.
+ * <li>Reader selects AID "5" or "6": service C is invoked automatically,
+ * because all AIDs it has asked for are only registered by C,
+ * and there is no overlap.
+ * </ul>
+ *
+ */
+ public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
/**
- * @hide
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, whenever an AID of this category is selected,
+ * the user is asked which service he wants to use to handle
+ * the transaction, even if there is only one matching service.
*/
- public static final String PAYMENT_MODE_AUTO = "auto";
+ public static final int SELECTION_MODE_ALWAYS_ASK = 1;
/**
- * @hide
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user will only be asked to select a service
+ * if the selected AID has been registered by multiple applications.
*/
- public static final String PAYMENT_MODE_MANUAL = "manual";
+ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+ static boolean sIsInitialized = false;
+ static HashMap<Context, CardEmulationManager> sCardEmuManagers = new HashMap();
+ static INfcCardEmulation sService;
final Context mContext;
@@ -113,7 +167,7 @@ public final class CardEmulationManager {
throw new UnsupportedOperationException();
}
try {
- if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HCE)) {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
Log.e(TAG, "This device does not support card emulation");
throw new UnsupportedOperationException();
}
@@ -137,6 +191,10 @@ public final class CardEmulationManager {
* Allows an application to query whether a service is currently
* the default service to handle a card emulation category.
*
+ * <p>Note that if {@link #getSelectionModeForCategory(String)}
+ * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always
+ * return false.
+ *
* @param service The ComponentName of the service
* @param category The category
* @return whether service is currently the default service for the category.
@@ -147,6 +205,10 @@ public final class CardEmulationManager {
} catch (RemoteException e) {
// Try one more time
recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
try {
return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
category);
@@ -186,6 +248,33 @@ public final class CardEmulationManager {
}
/**
+ * Returns the application selection mode for the passed in category.
+ * Valid return values are:
+ * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+ * application for this category, which will be preferred.
+ * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+ * every time what app he would like to use in this category.
+ * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+ * to pick a service if there is a conflict.
+ * @param category The category, for example {@link #CATEGORY_PAYMENT}
+ * @return
+ */
+ public int getSelectionModeForCategory(String category) {
+ if (CATEGORY_PAYMENT.equals(category)) {
+ String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+ if (defaultComponent != null) {
+ return SELECTION_MODE_PREFER_DEFAULT;
+ } else {
+ return SELECTION_MODE_ALWAYS_ASK;
+ }
+ } else {
+ // All other categories are in "only ask if conflict" mode
+ return SELECTION_MODE_ASK_IF_CONFLICT;
+ }
+ }
+
+ /**
* @hide
*/
public boolean setDefaultServiceForCategory(ComponentName service, String category) {
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index cdc4adb..174acc0 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -4,13 +4,11 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.content.Intent;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
@@ -42,13 +40,31 @@ public abstract class HostApduService extends Service {
*/
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
+ "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+
+ /**
+ * The name of the meta-data element that contains
+ * more information about this service.
+ */
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.host_apdu_service";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * TODO Remove
+ * @hide
+ */
+ public static final String OLD_SERVICE_INTERFACE =
"android.nfc.HostApduService";
/**
* The name of the meta-data element that contains
* more information about this service.
+ *
+ * TODO Remove
+ * @hide
*/
- public static final String SERVICE_META_DATA = "android.nfc.HostApduService";
+ public static final String OLD_SERVICE_META_DATA = "android.nfc.HostApduService";
/**
* Reason for {@link #onDeactivated(int)}.
@@ -65,7 +81,7 @@ public abstract class HostApduService extends Service {
* currently active on the logical channel).
*
* <p>Note that this next AID may still be resolved to this
- * service, in which case {@link #processCommandApdu(byte[], int)}
+ * service, in which case {@link #processCommandApdu(byte[], Bundle)}
* will be called again.
*/
public static final int DEACTIVATION_DESELECTED = 1;
@@ -133,7 +149,7 @@ public abstract class HostApduService extends Service {
byte[] apdu = dataBundle.getByteArray(KEY_DATA);
if (apdu != null) {
- byte[] responseApdu = processCommandApdu(apdu, 0);
+ byte[] responseApdu = processCommandApdu(apdu, null);
if (responseApdu != null) {
if (mNfcService == null) {
Log.e(TAG, "Response not sent; service was deactivated.");
@@ -232,7 +248,7 @@ public abstract class HostApduService extends Service {
* transaction.
*
* <p>Note: this method may be called anywhere between
- * the first {@link #processCommandApdu(byte[], int)}
+ * the first {@link #processCommandApdu(byte[], Bundle)}
* call and a {@link #onDeactivated(int)} call.
*/
public final void notifyUnhandled() {
@@ -292,8 +308,11 @@ public abstract class HostApduService extends Service {
* @param flags
* @return a byte-array containing the response APDU, or null if no
* response APDU can be sent at this point.
+ * @hide
*/
- public abstract byte[] processCommandApdu(byte[] commandApdu, int flags);
+ public byte[] processCommandApdu(byte[] commandApdu, int flags) {
+ return null;
+ }
/**
* This method will be called in two possible scenarios:
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java
index 79599db..15f63f9 100644
--- a/core/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/core/java/android/nfc/cardemulation/OffHostApduService.java
@@ -42,13 +42,14 @@ public abstract class OffHostApduService extends Service {
*/
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.nfc.OffHostApduService";
+ "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
/**
* The name of the meta-data element that contains
* more information about this service.
*/
- public static final String SERVICE_META_DATA = "android.nfc.OffHostApduService";
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.off_host_apdu_service";
/**
* The Android platform itself will not bind to this service,
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index f474504..32b1b60 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -22,6 +22,7 @@ import android.util.SparseArray;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
/**
@@ -545,6 +546,13 @@ public final class Bundle implements Parcelable, Cloneable {
mFdsKnown = false;
}
+ /** {@hide} */
+ public void putParcelableList(String key, List<? extends Parcelable> value) {
+ unparcel();
+ mMap.put(key, value);
+ mFdsKnown = false;
+ }
+
/**
* Inserts a SparceArray of Parcelable values into the mapping of this
* Bundle, replacing any existing value for the given key. Either key
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 0a6db25..60ce132 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -639,16 +639,19 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
*
* @hide
*/
- public static void startMethodTracingDdms(int bufferSize, int flags) {
- VMDebug.startMethodTracingDdms(bufferSize, flags);
+ public static void startMethodTracingDdms(int bufferSize, int flags,
+ boolean samplingEnabled, int intervalUs) {
+ VMDebug.startMethodTracingDdms(bufferSize, flags, samplingEnabled, intervalUs);
}
/**
- * Determine whether method tracing is currently active.
+ * Determine whether method tracing is currently active and what type is
+ * active.
+ *
* @hide
*/
- public static boolean isMethodTracingActive() {
- return VMDebug.isMethodTracingActive();
+ public static int getMethodTracingMode() {
+ return VMDebug.getMethodTracingMode();
}
/**
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index 3bfd9a1..fb6bb2e 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -41,7 +41,9 @@ interface IPrintManager {
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 validatePrinters(in List<PrinterId> printerIds, int userId);
+ void startPrinterStateTracking(in PrinterId printerId, int userId);
+ void stopPrinterStateTracking(in PrinterId printerId, int userId);
void destroyPrinterDiscoverySession(in IPrinterDiscoveryObserver observer,
int userId);
}
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index ba455f6..cdcd0c7 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -42,8 +42,6 @@ public final class PageRange implements Parcelable {
* @throws IllegalArgumentException If start is less than zero.
* @throws IllegalArgumentException If end is less than zero.
* @throws IllegalArgumentException If start greater than end.
- *
- * @hide
*/
public PageRange(int start, int end) {
if (start < 0) {
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index a902c72..caa10ae 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -50,9 +50,15 @@ public final class PrintAttributes implements Parcelable {
/** Fitting mode: No fitting. */
- public static final int FITTING_MODE_NONE = 0x00000001;
- /** Fitting mode: Fit the content to the page. */
- public static final int FITTING_MODE_FIT_TO_PAGE = 0x00000002;
+ public static final int FITTING_MODE_NONE = 1 << 0;
+ /** Fitting mode: Scale the content to fit in the page
+ * without cropping it in any dimension. */
+ public static final int FITTING_MODE_SCALE_TO_FIT = 1 << 1;
+ /**
+ * Fitting mode: Uniformly scale the content to fill the entire page
+ * potentially cropping the content if it overflows in one dimension.
+ */
+ public static final int FITTING_MODE_SCALE_TO_FILL = 1 << 2;
private static final int VALID_DUPLEX_MODES =
@@ -62,7 +68,7 @@ public final class PrintAttributes implements Parcelable {
COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR;
private static final int VALID_FITTING_MODES =
- FITTING_MODE_NONE | FITTING_MODE_FIT_TO_PAGE;
+ FITTING_MODE_NONE | FITTING_MODE_SCALE_TO_FIT | FITTING_MODE_SCALE_TO_FILL;
private static final int VALID_ORIENTATIONS =
ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE;
@@ -252,7 +258,8 @@ public final class PrintAttributes implements Parcelable {
* @return The fitting mode or zero if not set.
*
* @see #FITTING_MODE_NONE
- * @see #FITTING_MODE_FIT_TO_PAGE
+ * @see #FITTING_MODE_SCALE_TO_FILL
+ * @see #FITTING_MODE_SCALE_TO_FIT
*/
public int getFittingMode() {
return mFittingMode;
@@ -264,12 +271,13 @@ public final class PrintAttributes implements Parcelable {
* @param The fitting mode.
*
* @see #FITTING_MODE_NONE
- * @see #FITTING_MODE_FIT_TO_PAGE
+ * @see #FITTING_MODE_SCALE_TO_FILL
+ * @see #FITTING_MODE_SCALE_TO_FIT
*
* @hide
*/
public void setFittingMode(int fittingMode) {
- enfoceValidFittingMode(fittingMode);
+ enforceValidFittingMode(fittingMode);
mFittingMode = fittingMode;
}
@@ -1220,6 +1228,8 @@ public final class PrintAttributes implements Parcelable {
* This class specifies content margins.
*/
public static final class Margins {
+ public static final Margins NO_MARGINS = new Margins(0, 0, 0, 0);
+
private final int mLeftMils;
private final int mTopMils;
private final int mRightMils;
@@ -1232,24 +1242,13 @@ public final class PrintAttributes implements Parcelable {
* @param topMils The top margin in mils (thousands of an inch).
* @param rightMils The right margin in mils (thousands of an inch).
* @param bottomMils The bottom margin in mils (thousands of an inch).
- *
- * @throws IllegalArgumentException If the leftMils is less than zero.
- * @throws IllegalArgumentException If the topMils is less than zero.
- * @throws IllegalArgumentException If the rightMils is less than zero.
- * @throws IllegalArgumentException If the bottomMils is less than zero.
*/
public Margins(int leftMils, int topMils, int rightMils, int bottomMils) {
- if (leftMils < 0) {
- throw new IllegalArgumentException("leftMils cannot be less than zero.");
+ if (leftMils > rightMils) {
+ throw new IllegalArgumentException("leftMils cannot be less than rightMils.");
}
- if (topMils < 0) {
- throw new IllegalArgumentException("topMils cannot be less than zero.");
- }
- if (rightMils < 0) {
- throw new IllegalArgumentException("rightMils cannot be less than zero.");
- }
- if (bottomMils < 0) {
- throw new IllegalArgumentException("bottomMils cannot be less than zero.");
+ if (topMils > bottomMils) {
+ throw new IllegalArgumentException("topMils cannot be less than bottomMils.");
}
mTopMils = topMils;
mLeftMils = leftMils;
@@ -1504,8 +1503,11 @@ public final class PrintAttributes implements Parcelable {
case FITTING_MODE_NONE: {
return "FITTING_MODE_NONE";
}
- case FITTING_MODE_FIT_TO_PAGE: {
- return "FITTING_MODE_FIT_TO_PAGE";
+ case FITTING_MODE_SCALE_TO_FIT: {
+ return "FITTING_MODE_SCALE_TO_FIT";
+ }
+ case FITTING_MODE_SCALE_TO_FILL: {
+ return "FITTING_MODE_SCALE_TO_FILL";
}
default:
return "FITTING_MODE_UNKNOWN";
@@ -1513,25 +1515,25 @@ public final class PrintAttributes implements Parcelable {
}
static void enforceValidDuplexMode(int duplexMode) {
- if ((duplexMode & VALID_DUPLEX_MODES) == 0) {
+ if ((duplexMode & VALID_DUPLEX_MODES) == 0 && Integer.bitCount(duplexMode) == 1) {
throw new IllegalArgumentException("invalid duplex mode: " + duplexMode);
}
}
static void enforceValidColorMode(int colorMode) {
- if ((colorMode & VALID_COLOR_MODES) == 0) {
+ if ((colorMode & VALID_COLOR_MODES) == 0 && Integer.bitCount(colorMode) == 1) {
throw new IllegalArgumentException("invalid color mode: " + colorMode);
}
}
- static void enfoceValidFittingMode(int fittingMode) {
- if ((fittingMode & VALID_FITTING_MODES) == 0) {
+ static void enforceValidFittingMode(int fittingMode) {
+ if ((fittingMode & VALID_FITTING_MODES) == 0 && Integer.bitCount(fittingMode) == 1) {
throw new IllegalArgumentException("invalid fitting mode: " + fittingMode);
}
}
static void enforceValidOrientation(int orientation) {
- if ((orientation & VALID_ORIENTATIONS) == 0) {
+ if ((orientation & VALID_ORIENTATIONS) == 0 && Integer.bitCount(orientation) == 1) {
throw new IllegalArgumentException("invalid orientation: " + orientation);
}
}
diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java
index d320226..33b4aad 100644
--- a/core/java/android/print/PrintDocumentAdapter.java
+++ b/core/java/android/print/PrintDocumentAdapter.java
@@ -18,8 +18,8 @@ package android.print;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
-import java.io.FileDescriptor;
import java.util.List;
/**
@@ -41,7 +41,7 @@ import java.util.List;
* <li>
* After every call to {@link #onLayout(PrintAttributes, PrintAttributes,
* CancellationSignal, LayoutResultCallback, Bundle)}, you may get a call to
- * {@link #onWrite(PageRange[], FileDescriptor, CancellationSignal, WriteResultCallback)}
+ * {@link #onWrite(PageRange[], ParcelFileDescriptor, CancellationSignal, WriteResultCallback)}
* asking you to write a PDF file with the content for specific pages.
* </li>
* <li>
@@ -64,7 +64,7 @@ import java.util.List;
* PrintAttributes, CancellationSignal, LayoutResultCallback, Bundle)} on
* the UI thread (assuming onStart initializes resources needed for layout).
* This will ensure that the UI does not change while you are laying out the
- * printed content. Then you can handle {@link #onWrite(PageRange[], FileDescriptor,
+ * printed content. Then you can handle {@link #onWrite(PageRange[], ParcelFileDescriptor,
* CancellationSignal, WriteResultCallback)} and {@link #onFinish()} on another
* thread. This will ensure that the UI is frozen for the minimal amount of
* time. Also this assumes that you will generate the printed content in
@@ -101,6 +101,28 @@ public abstract class PrintDocumentAdapter {
* LayoutResultCallback#onLayoutFailed(CharSequence)}, if an error occurred.
* </p>
* <p>
+ * When doing a layout you may satisfy some of the constraints in the print
+ * attributes such as applying the appropriate fitting, emitting content in the
+ * requested orientation, using the specified margins, generating content with
+ * the desired color mode, producing output with the given media size. Ideally,
+ * you will satisfy all of these constraints. It is important that if you
+ * satisfy a given constraint, you update the {@link PrintDocumentInfo} that
+ * is returned in the given {@link LayoutResultCallback}. This way the printer
+ * will have more accurate information about the content, thus producing a
+ * better output. For example, assume that your application is printing
+ * an image and the print attributes request landscape and fitting mode scale
+ * to fill. The result of this operation should be the entire media is filled
+ * and the content is rotated ninety degrees. In this case it is beneficial
+ * you do the rotation and select a higher resolution image to utilize
+ * the wider media (the height is now the width), rather to use a lower
+ * resolution image that is later stretched by the printer. If you applied
+ * the rotation you have to update the returned print document info to
+ * reflect that the content is already in landscape by calling
+ * {@link PrintDocumentInfo.Builder#setOrientation(int)} with {@link
+ * PrintAttributes#ORIENTATION_LANDSCAPE}. In this case the printer does not
+ * have to rotate the content.
+ * </p>
+ * <p>
* <strong>Note:</strong> If the content is large and a layout will be
* performed, it is a good practice to schedule the work on a dedicated
* thread and register an observer in the provided {@link
@@ -128,10 +150,10 @@ public abstract class PrintDocumentAdapter {
* from of a PDF file to the given file descriptor. This method is invoked
* on the main thread.
*<p>
- * After you are done writing, you should <strong>not</strong> close the
- * file descriptor, rather you must invoke: {@link WriteResultCallback
- * #onWriteFinished(List)}, if writing completed successfully; or {@link
- * WriteResultCallback#onWriteFailed(CharSequence)}, if an error occurred.
+ * After you are done writing, you should close the file descriptor and
+ * invoke {@link WriteResultCallback #onWriteFinished(List)}, if writing
+ * completed successfully; or {@link WriteResultCallback#onWriteFailed(
+ * CharSequence)}, if an error occurred.
* </p>
* <p>
* <strong>Note:</strong> If the printed content is large, it is a good
@@ -149,7 +171,7 @@ public abstract class PrintDocumentAdapter {
* @see WriteResultCallback
* @see CancellationSignal
*/
- public abstract void onWrite(PageRange[] pages, FileDescriptor destination,
+ public abstract void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback);
/**
@@ -163,7 +185,7 @@ public abstract class PrintDocumentAdapter {
/**
* Base class for implementing a callback for the result of {@link
- * PrintDocumentAdapter#onWrite(PageRange[], FileDescriptor, CancellationSignal,
+ * PrintDocumentAdapter#onWrite(PageRange[], ParcelFileDescriptor, CancellationSignal,
* WriteResultCallback)}.
*/
public static abstract class WriteResultCallback {
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index 653ad4b..f2b91ae 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -18,6 +18,8 @@ package android.print;
import android.os.Parcel;
import android.os.Parcelable;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
import android.text.TextUtils;
/**
@@ -26,12 +28,17 @@ import android.text.TextUtils;
public final class PrintDocumentInfo implements Parcelable {
/**
- * Constant for unknown page count (default).
+ * Constant for an unknown media size.
+ */
+ public static final MediaSize MEDIA_SIZE_UNKNOWN = new MediaSize("Unknown", "Unknown", 1, 1);
+
+ /**
+ * Constant for unknown page count..
*/
public static final int PAGE_COUNT_UNKNOWN = -1;
/**
- * Content type: unknown (default).
+ * Content type: unknown.
*/
public static final int CONTENT_TYPE_UNKNOWN = -1;
@@ -48,13 +55,18 @@ public final class PrintDocumentInfo implements Parcelable {
private String mName;
private int mPageCount;
private int mContentType;
+ private int mOrientation;
+ private int mFittingMode;
+ private int mColorMode;
+ private Margins mMargins;
+ private MediaSize mMediaSize;
+ private long mDataSize;
/**
* Creates a new instance.
*/
private PrintDocumentInfo() {
- mPageCount = PAGE_COUNT_UNKNOWN;
- mContentType = CONTENT_TYPE_UNKNOWN;
+ /* do nothing */
}
/**
@@ -66,6 +78,12 @@ public final class PrintDocumentInfo implements Parcelable {
mName = prototype.mName;
mPageCount = prototype.mPageCount;
mContentType = prototype.mContentType;
+ mOrientation = prototype.mOrientation;
+ mFittingMode = prototype.mFittingMode;
+ mColorMode = prototype.mColorMode;
+ mMargins = prototype.mMargins;
+ mMediaSize = prototype.mMediaSize;
+ mDataSize = prototype.mDataSize;
}
/**
@@ -77,6 +95,12 @@ public final class PrintDocumentInfo implements Parcelable {
mName = parcel.readString();
mPageCount = parcel.readInt();
mContentType = parcel.readInt();
+ mOrientation = parcel.readInt();
+ mFittingMode = parcel.readInt();
+ mColorMode = parcel.readInt();
+ mMargins = Margins.createFromParcel(parcel);
+ mMediaSize = MediaSize.createFromParcel(parcel);
+ mDataSize = parcel.readLong();
}
/**
@@ -112,6 +136,81 @@ public final class PrintDocumentInfo implements Parcelable {
return mContentType;
}
+ /**
+ * Gets the document orientation.
+ *
+ * @return The orientation.
+ *
+ * @see PrintAttributes#ORIENTATION_PORTRAIT PrintAttributes.ORIENTATION_PORTRAIT
+ * @see PrintAttributes#ORIENTATION_LANDSCAPE PrintAttributes.ORIENTATION_LANDSCAPE
+ */
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * Gets the document fitting mode.
+ *
+ * @return The fitting mode.
+ *
+ * @see PrintAttributes#FITTING_MODE_NONE PrintAttributes.FITTING_MODE_NONE
+ * @see PrintAttributes#FITTING_MODE_SCALE_TO_FILL PrintAttributes.FITTING_MODE_SCALE_TO_FILL
+ * @see PrintAttributes#FITTING_MODE_SCALE_TO_FIT PrintAttributes.FITTING_MODE_SCALE_TO_FIT
+ */
+ public int getFittingMode() {
+ return mFittingMode;
+ }
+
+ /**
+ * Gets document color mode.
+ *
+ * @return The color mode.
+ *
+ * @see PrintAttributes#COLOR_MODE_COLOR PrintAttributes.COLOR_MODE_COLOR
+ * @see PrintAttributes#COLOR_MODE_MONOCHROME PrintAttributes.COLOR_MODE_MONOCHROME
+ */
+ public int getColorMode() {
+ return mColorMode;
+ }
+
+ /**
+ * Gets the document margins.
+ *
+ * @return The margins.
+ */
+ public Margins getMargins() {
+ return mMargins;
+ }
+
+ /**
+ * Gets the media size.
+ *
+ * @return The media size.
+ */
+ public MediaSize getMediaSize() {
+ return mMediaSize;
+ }
+
+ /**
+ * Gets the document data size in bytes.
+ *
+ * @return The data size.
+ */
+ public long getDataSize() {
+ return mDataSize;
+ }
+
+ /**
+ * Sets the document data size in bytes.
+ *
+ * @param dataSize The data size.
+ *
+ * @hide
+ */
+ public void setDataSize(long dataSize) {
+ mDataSize = dataSize;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -122,6 +221,12 @@ public final class PrintDocumentInfo implements Parcelable {
parcel.writeString(mName);
parcel.writeInt(mPageCount);
parcel.writeInt(mContentType);
+ parcel.writeInt(mOrientation);
+ parcel.writeInt(mFittingMode);
+ parcel.writeInt(mColorMode);
+ mMargins.writeToParcel(parcel);
+ mMediaSize.writeToParcel(parcel);
+ parcel.writeLong(mDataSize);
}
@Override
@@ -131,6 +236,13 @@ public final class PrintDocumentInfo implements Parcelable {
result = prime * result + ((mName != null) ? mName.hashCode() : 0);
result = prime * result + mContentType;
result = prime * result + mPageCount;
+ result = prime * result + mOrientation;
+ result = prime * result + mFittingMode;
+ result = prime * result + mColorMode;
+ result = prime * result + (mMargins != null ? mMargins.hashCode() : 0);
+ result = prime * result + (mMediaSize != null ? mMediaSize.hashCode() : 0);
+ result = prime * result + (int) mDataSize;
+ result = prime * result + (int) mDataSize >> 32;
return result;
}
@@ -155,6 +267,32 @@ public final class PrintDocumentInfo implements Parcelable {
if (mPageCount != other.mPageCount) {
return false;
}
+ if (mOrientation != other.mOrientation) {
+ return false;
+ }
+ if (mFittingMode != other.mFittingMode) {
+ return false;
+ }
+ if (mColorMode != other.mColorMode) {
+ return false;
+ }
+ if (mMargins == null) {
+ if (other.mMargins != null) {
+ return false;
+ }
+ } else if (!mMargins.equals(other.mMargins)) {
+ return false;
+ }
+ if (mMediaSize == null) {
+ if (other.mMediaSize != null) {
+ return false;
+ }
+ } else if (!mMediaSize.equals(other.mMediaSize)) {
+ return false;
+ }
+ if (mDataSize != other.mDataSize) {
+ return false;
+ }
return true;
}
@@ -165,6 +303,12 @@ public final class PrintDocumentInfo implements Parcelable {
builder.append("name=").append(mName);
builder.append(", pageCount=").append(mPageCount);
builder.append(", contentType=").append(contentTyepToString(mContentType));
+ builder.append(", orientation=").append(PrintAttributes.orientationToString(mOrientation));
+ builder.append(", fittingMode=").append(PrintAttributes.fittingModeToString(mFittingMode));
+ builder.append(", colorMode=").append(PrintAttributes.colorModeToString(mColorMode));
+ builder.append(", margins=").append(mMargins);
+ builder.append(", mediaSize=").append(mMediaSize);
+ builder.append(", size=").append(mDataSize);
builder.append("}");
return builder.toString();
}
@@ -191,21 +335,62 @@ public final class PrintDocumentInfo implements Parcelable {
/**
* Constructor.
+ * <p>
+ * The values of the relevant properties are initialized from the
+ * provided print attributes. For example, the orientation is set
+ * to be the same as the orientation returned by calling {@link
+ * PrintAttributes#getOrientation() PrintAttributes.getOrientation()}.
+ * </p>
*
* @param name The document name. Cannot be empty.
+ * @param attributes Print attributes. Cannot be null.
*
* @throws IllegalArgumentException If the name is empty.
*/
+ public Builder(String name, PrintAttributes attributes) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name cannot be empty");
+ }
+ if (attributes == null) {
+ throw new IllegalArgumentException("attributes cannot be null");
+ }
+ mPrototype = new PrintDocumentInfo();
+ mPrototype.mName = name;
+ mPrototype.mOrientation = attributes.getOrientation();
+ mPrototype.mFittingMode = attributes.getFittingMode();
+ mPrototype.mColorMode = attributes.getColorMode();
+ mPrototype.mMargins = attributes.getMargins();
+ mPrototype.mMediaSize = attributes.getMediaSize();
+ }
+
+ /**
+ * Constructor.
+ * <p>
+ * The values of the relevant properties are initialized with default
+ * values. Please refer to the documentation of the individual setters
+ * for information about the default values.
+ * </p>
+ *
+ * @param name The document name. Cannot be empty.
+ */
public Builder(String name) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("name cannot be empty");
}
mPrototype = new PrintDocumentInfo();
mPrototype.mName = name;
+ mPrototype.mOrientation = PrintAttributes.ORIENTATION_PORTRAIT;
+ mPrototype.mFittingMode = PrintAttributes.FITTING_MODE_NONE;
+ mPrototype.mColorMode = PrintAttributes.COLOR_MODE_COLOR;
+ mPrototype.mMargins = Margins.NO_MARGINS;
+ mPrototype.mMediaSize = MEDIA_SIZE_UNKNOWN;
}
/**
* Sets the total number of pages.
+ * <p>
+ * <strong>Default: </strong> {@link #PAGE_COUNT_UNKNOWN}
+ * </p>
*
* @param pageCount The number of pages. Must be greater than
* or equal to zero or {@link PrintDocumentInfo#PAGE_COUNT_UNKNOWN}.
@@ -222,6 +407,9 @@ public final class PrintDocumentInfo implements Parcelable {
/**
* Sets the content type.
+ * <p>
+ * <strong>Default: </strong> {@link #CONTENT_TYPE_UNKNOWN}
+ * </p>
*
* @param type The content type.
*
@@ -235,6 +423,95 @@ public final class PrintDocumentInfo implements Parcelable {
}
/**
+ * Sets the orientation.
+ * <p>
+ * <strong>Default: </strong> {@link PrintAttributes#ORIENTATION_PORTRAIT
+ * PrintAttributes.ORIENTATION_PORTRAIT}
+ * </p>
+ *
+ * @param orientation The orientation.
+ *
+ * @see PrintAttributes#ORIENTATION_PORTRAIT PrintAttributes.ORIENTATION_PORTRAIT
+ * @see PrintAttributes#ORIENTATION_LANDSCAPE PrintAttributes.ORIENTATION_LANDSCAPE
+ */
+ public Builder setOrientation(int orientation) {
+ PrintAttributes.enforceValidOrientation(orientation);
+ mPrototype.mOrientation = orientation;
+ return this;
+ }
+
+ /**
+ * Sets the content fitting mode.
+ * <p>
+ * <strong>Default: </strong> {@link PrintAttributes#FITTING_MODE_NONE
+ * PrintAttributes.FITTING_MODE_NONE}
+ * </p>
+ *
+ * @param fittingMode The fitting mode.
+ *
+ * @see PrintAttributes#FITTING_MODE_NONE PrintAttributes.FITTING_MODE_NONE
+ * @see PrintAttributes#FITTING_MODE_SCALE_TO_FILL PrintAttributes.FITTING_MODE_SCALE_TO_FILL
+ * @see PrintAttributes#FITTING_MODE_SCALE_TO_FIT PrintAttributes.FITTING_MODE_SCALE_TO_FIT
+ */
+ public Builder setFittingMode(int fittingMode) {
+ PrintAttributes.enforceValidFittingMode(fittingMode);
+ mPrototype.mFittingMode = fittingMode;
+ return this;
+ }
+
+ /**
+ * Sets the content color mode.
+ * <p>
+ * <strong>Default: </strong> {@link PrintAttributes#COLOR_MODE_COLOR
+ * PrintAttributes.COLOR_MODE_COLOR}
+ * </p>
+ *
+ * @param colorMode The color mode.
+ *
+ * @see PrintAttributes#COLOR_MODE_COLOR PrintAttributes.COLOR_MODE_COLOR
+ * @see PrintAttributes#COLOR_MODE_MONOCHROME PrintAttributes.COLOR_MODE_MONOCHROME
+ */
+ public Builder setColorMode(int colorMode) {
+ PrintAttributes.enforceValidColorMode(colorMode);
+ mPrototype.mColorMode = colorMode;
+ return this;
+ }
+
+ /**
+ * Sets the document margins.
+ * <p>
+ * <strong>Default: </strong> {@link PrintAttributes.Margins#NO_MARGINS Margins.NO_MARGINS}
+ * </p>
+ *
+ * @param margins The margins. Cannot be null.
+ */
+ public Builder setMargins(Margins margins) {
+ if (margins == null) {
+ throw new IllegalArgumentException("margins cannot be null");
+ }
+ mPrototype.mMargins = margins;
+ return this;
+ }
+
+ /**
+ * Sets the document media size.
+ * <p>
+ * <strong>Default: </strong>#MEDIA_SIZE_UNKNOWN
+ * </p>
+ *
+ * @param mediaSize The media size. Cannot be null.
+ *
+ * @see #MEDIA_SIZE_UNKNOWN
+ */
+ public Builder setMediaSize(MediaSize mediaSize) {
+ if (mediaSize == null) {
+ throw new IllegalArgumentException("media size cannot be null");
+ }
+ mPrototype.mMediaSize = mediaSize;
+ return this;
+ }
+
+ /**
* Creates a new {@link PrintDocumentInfo} instance.
*
* @return The new instance.
diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
index dbc8b6f..b905396 100644
--- a/core/java/android/print/PrintFileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -21,6 +21,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
+import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.android.internal.R;
@@ -28,7 +29,6 @@ import com.android.internal.R;
import libcore.io.IoUtils;
import java.io.File;
-import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -81,7 +81,7 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter {
}
@Override
- public void onWrite(PageRange[] pages, FileDescriptor destination,
+ public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback) {
mWriteFileAsyncTask = new WriteFileAsyncTask(destination, cancellationSignal, callback);
mWriteFileAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
@@ -90,13 +90,13 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter {
private final class WriteFileAsyncTask extends AsyncTask<Void, Void, Void> {
- private final FileDescriptor mDestination;
+ private final ParcelFileDescriptor mDestination;
private final WriteResultCallback mResultCallback;
private final CancellationSignal mCancellationSignal;
- public WriteFileAsyncTask(FileDescriptor destination,
+ public WriteFileAsyncTask(ParcelFileDescriptor destination,
CancellationSignal cancellationSignal, WriteResultCallback callback) {
mDestination = destination;
mResultCallback = callback;
@@ -112,7 +112,7 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter {
@Override
protected Void doInBackground(Void... params) {
InputStream in = null;
- OutputStream out = new FileOutputStream(mDestination);
+ OutputStream out = new FileOutputStream(mDestination.getFileDescriptor());
final byte[] buffer = new byte[8192];
try {
in = new FileInputStream(mFile);
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 602f3c1..b919ad6 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -44,6 +44,13 @@ public final class PrintJobInfo implements Parcelable {
public static final int STATE_ANY_VISIBLE_TO_CLIENTS = -2;
/**
+ * Constant for matching any active print job state.
+ *
+ * @hide
+ */
+ public static final int STATE_ANY_ACTIVE = -3;
+
+ /**
* Print job state: The print job is being created but not yet
* ready to be printed.
* <p>
@@ -55,7 +62,7 @@ public final class PrintJobInfo implements Parcelable {
public static final int STATE_CREATED = 1;
/**
- * Print job status: The print jobs is created, it is ready
+ * Print job state: The print jobs is created, it is ready
* to be printed and should be processed.
* <p>
* Next valid states: {@link #STATE_STARTED}, {@link #STATE_FAILED},
@@ -65,40 +72,49 @@ public final class PrintJobInfo implements Parcelable {
public static final int STATE_QUEUED = 2;
/**
- * Print job status: The print job is being printed.
+ * Print job state: The print job is being printed.
* <p>
* Next valid states: {@link #STATE_COMPLETED}, {@link #STATE_FAILED},
- * {@link #STATE_CANCELED}
+ * {@link #STATE_CANCELED}, {@link #STATE_BLOCKED}
* </p>
*/
public static final int STATE_STARTED = 3;
/**
- * Print job status: The print job was successfully printed.
+ * Print job state: The print job is blocked.
+ * <p>
+ * Next valid states: {@link #STATE_FAILED}, {@link #STATE_CANCELED},
+ * {@link #STATE_STARTED}
+ * </p>
+ */
+ public static final int STATE_BLOCKED = 4;
+
+ /**
+ * Print job state: The print job was successfully printed.
* This is a terminal state.
* <p>
* Next valid states: None
* </p>
*/
- public static final int STATE_COMPLETED = 4;
+ public static final int STATE_COMPLETED = 5;
/**
- * Print job status: The print job was printing but printing failed.
+ * Print job state: The print job was printing but printing failed.
* This is a terminal state.
* <p>
* Next valid states: None
* </p>
*/
- public static final int STATE_FAILED = 5;
+ public static final int STATE_FAILED = 6;
/**
- * Print job status: The print job was canceled.
+ * Print job state: The print job was canceled.
* This is a terminal state.
* <p>
* Next valid states: None
* </p>
*/
- public static final int STATE_CANCELED = 6;
+ public static final int STATE_CANCELED = 7;
/** The unique print job id. */
private int mId;
@@ -127,8 +143,8 @@ public final class PrintJobInfo implements Parcelable {
/** How many copies to print. */
private int mCopies;
- /** Failure reason if this job failed. */
- private String mFailureReason;
+ /** Reason for the print job being in its current state. */
+ private String mStateReason;
/** The pages to print */
private PageRange[] mPageRanges;
@@ -155,7 +171,7 @@ public final class PrintJobInfo implements Parcelable {
mUserId = other.mUserId;
mTag = other.mTag;
mCopies = other.mCopies;
- mFailureReason = other.mFailureReason;
+ mStateReason = other.mStateReason;
mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
@@ -171,7 +187,7 @@ public final class PrintJobInfo implements Parcelable {
mUserId = parcel.readInt();
mTag = parcel.readString();
mCopies = parcel.readInt();
- mFailureReason = parcel.readString();
+ mStateReason = parcel.readString();
if (parcel.readInt() == 1) {
Parcelable[] parcelables = parcel.readParcelableArray(null);
mPageRanges = new PageRange[parcelables.length];
@@ -377,25 +393,27 @@ public final class PrintJobInfo implements Parcelable {
}
/**
- * The failure reason if this print job failed.
+ * Gets the reason for the print job being in the current state.
*
- * @return The failure reason.
+ * @return The reason, or null if there is no reason or the
+ * reason is unknown.
*
* @hide
*/
- public String getFailureReason() {
- return mFailureReason;
+ public String getStateReason() {
+ return mStateReason;
}
/**
- * The failure reason if this print job failed.
+ * Sets the reason for the print job being in the current state.
*
- * @param failureReason The failure reason.
+ * @param stateReason The reason, or null if there is no reason
+ * or the reason is unknown.
*
* @hide
*/
- public void setFailureReason(String failureReason) {
- mFailureReason = failureReason;
+ public void setStateReason(String stateReason) {
+ mStateReason = stateReason;
}
/**
@@ -476,7 +494,7 @@ public final class PrintJobInfo implements Parcelable {
parcel.writeInt(mUserId);
parcel.writeString(mTag);
parcel.writeInt(mCopies);
- parcel.writeString(mFailureReason);
+ parcel.writeString(mStateReason);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index d3e35c3..6e32c05 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -36,7 +36,6 @@ import com.android.internal.os.SomeArgs;
import libcore.io.IoUtils;
import java.io.File;
-import java.io.FileDescriptor;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
@@ -163,7 +162,7 @@ public final class PrintManager {
* @param pdfFile The PDF file to print.
* @param documentInfo Information about the printed document.
* @param attributes The default print job attributes.
- * @return The created print job.
+ * @return The created print job on success or null on failure.
*
* @see PrintJob
*/
@@ -181,7 +180,7 @@ public final class PrintManager {
* @param printJobName A name for the new print job.
* @param documentAdapter An adapter that emits the document to print.
* @param attributes The default print job attributes.
- * @return The created print job.
+ * @return The created print job on success or null on failure.
*
* @see PrintJob
*/
@@ -279,7 +278,7 @@ public final class PrintManager {
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = pages;
- args.arg2 = fd.getFileDescriptor();
+ args.arg2 = fd;
args.arg3 = callback;
args.argi1 = sequence;
mHandler.removeMessages(MyHandler.MSG_WRITE);
@@ -342,7 +341,7 @@ public final class PrintManager {
case MSG_WRITE: {
SomeArgs args = (SomeArgs) message.obj;
PageRange[] pages = (PageRange[]) args.arg1;
- FileDescriptor fd = (FileDescriptor) args.arg2;
+ ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg2;
IWriteResultCallback callback = (IWriteResultCallback) args.arg3;
final int sequence = args.argi1;
args.recycle();
@@ -428,12 +427,12 @@ public final class PrintManager {
}
private final class MyWriteResultCallback extends WriteResultCallback {
- private FileDescriptor mFd;
+ private ParcelFileDescriptor mFd;
private int mSequence;
private IWriteResultCallback mCallback;
public MyWriteResultCallback(IWriteResultCallback callback,
- FileDescriptor fd, int sequence) {
+ ParcelFileDescriptor fd, int sequence) {
mFd = fd;
mSequence = sequence;
mCallback = callback;
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
index 70b418c..941e6e1 100644
--- a/core/java/android/print/PrinterCapabilitiesInfo.java
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -863,12 +863,12 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
while (currentModes > 0) {
final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
currentModes &= ~currentMode;
- PrintAttributes.enfoceValidFittingMode(currentMode);
+ PrintAttributes.enforceValidFittingMode(currentMode);
}
if ((fittingModes & defaultFittingMode) == 0) {
throw new IllegalArgumentException("Default fitting mode not in fiting modes.");
}
- PrintAttributes.enfoceValidFittingMode(defaultFittingMode);
+ PrintAttributes.enforceValidFittingMode(defaultFittingMode);
mPrototype.mFittingModes = fittingModes;
mPrototype.mDefaults[PROPERTY_FITTING_MODE] = defaultFittingMode;
return this;
diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java
index 8fbdd9c..46f0bef 100644
--- a/core/java/android/print/PrinterDiscoverySession.java
+++ b/core/java/android/print/PrinterDiscoverySession.java
@@ -74,6 +74,7 @@ public final class PrinterDiscoverySession {
public final void startPrinterDisovery(List<PrinterId> priorityList) {
if (isDestroyed()) {
Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed");
+ return;
}
if (!mIsPrinterDiscoveryStarted) {
mIsPrinterDiscoveryStarted = true;
@@ -88,6 +89,7 @@ public final class PrinterDiscoverySession {
public final void stopPrinterDiscovery() {
if (isDestroyed()) {
Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed");
+ return;
}
if (mIsPrinterDiscoveryStarted) {
mIsPrinterDiscoveryStarted = false;
@@ -99,14 +101,39 @@ public final class PrinterDiscoverySession {
}
}
- public final void requestPrinterUpdate(PrinterId printerId) {
+ public final void startPrinterStateTracking(PrinterId printerId) {
+ if (isDestroyed()) {
+ Log.w(LOG_TAG, "Ignoring start printer state tracking - session destroyed");
+ return;
+ }
+ try {
+ mPrintManager.startPrinterStateTracking(printerId, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer state tracking", re);
+ }
+ }
+
+ public final void stopPrinterStateTracking(PrinterId printerId) {
if (isDestroyed()) {
- Log.w(LOG_TAG, "Ignoring reqeust printer update - session destroyed");
+ Log.w(LOG_TAG, "Ignoring stop printer state tracking - session destroyed");
+ return;
+ }
+ try {
+ mPrintManager.stopPrinterStateTracking(printerId, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error stoping printer state tracking", re);
+ }
+ }
+
+ public final void validatePrinters(List<PrinterId> printerIds) {
+ if (isDestroyed()) {
+ Log.w(LOG_TAG, "Ignoring validate printers - session destroyed");
+ return;
}
try {
- mPrintManager.requestPrinterUpdate(printerId, mUserId);
+ mPrintManager.validatePrinters(printerIds, mUserId);
} catch (RemoteException re) {
- Log.e(LOG_TAG, "Error requesting printer update", re);
+ Log.e(LOG_TAG, "Error validating printers", re);
}
}
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 6f567a6..0ea319b 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -25,10 +25,14 @@ import android.text.TextUtils;
*/
public final class PrinterInfo implements Parcelable {
- /** Printer status: the printer is ready to print. */
- public static final int STATUS_READY = 1;
+ /** Printer status: the printer is idle and ready to print. */
+ public static final int STATUS_IDLE = 1;
- // TODO: Add printer status constants.
+ /** Printer status: the printer is busy printing. */
+ public static final int STATUS_BUSY = 2;
+
+ /** Printer status: the printer is not available. */
+ public static final int STATUS_UNAVAILABLE = 3;
private PrinterId mId;
@@ -237,6 +241,21 @@ public final class PrinterInfo implements Parcelable {
}
/**
+ * Sets the printer status.
+ *
+ * @param status The status.
+ * @return This builder.
+ *
+ * @see PrinterInfo#STATUS_IDLE
+ * @see PrinterInfo#STATUS_BUSY
+ * @see PrinterInfo#STATUS_UNAVAILABLE
+ */
+ public Builder setStatus(int status) {
+ mPrototype.mStatus = status;
+ return this;
+ }
+
+ /**
* Sets the printer name.
*
* @param name The name.
@@ -279,7 +298,9 @@ public final class PrinterInfo implements Parcelable {
}
private boolean isValidStatus(int status) {
- return (status == PrinterInfo.STATUS_READY);
+ return (status == STATUS_IDLE
+ || status == STATUS_IDLE
+ || status == STATUS_UNAVAILABLE);
}
}
diff --git a/core/java/android/print/pdf/PdfDocument.java b/core/java/android/print/pdf/PdfDocument.java
index dbd7dd1..a2883cf 100644
--- a/core/java/android/print/pdf/PdfDocument.java
+++ b/core/java/android/print/pdf/PdfDocument.java
@@ -44,7 +44,7 @@ import java.util.List;
* PdfDocument document = PdfDocument.open();
*
* // crate a page description
- * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1, 300).create();
+ * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1).create();
*
* // start a page
* Page page = document.startPage(pageInfo);
@@ -125,8 +125,7 @@ public final class PdfDocument {
throw new IllegalStateException("Previous page not finished!");
}
Canvas canvas = new PdfCanvas(nativeCreatePage(pageInfo.mPageSize,
- pageInfo.mContentSize, pageInfo.mInitialTransform.native_instance),
- pageInfo.mDensity);
+ pageInfo.mContentSize, pageInfo.mInitialTransform.native_instance));
mCurrentPage = new Page(canvas, pageInfo);
return mCurrentPage;
}
@@ -230,25 +229,14 @@ public final class PdfDocument {
private final class PdfCanvas extends Canvas {
- public PdfCanvas(int nativeCanvas, int density) {
+ public PdfCanvas(int nativeCanvas) {
super(nativeCanvas);
- super.setDensity(density);
}
@Override
public void setBitmap(Bitmap bitmap) {
throw new UnsupportedOperationException();
}
-
- @Override
- public void setDensity(int density) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setScreenDensity(int density) {
- throw new UnsupportedOperationException();
- }
}
/**
@@ -259,7 +247,6 @@ public final class PdfDocument {
private Rect mContentSize;
private Matrix mInitialTransform;
private int mPageNumber;
- private int mDensity;
/**
* Creates a new instance.
@@ -269,7 +256,7 @@ public final class PdfDocument {
}
/**
- * Gets the page size in pixels.
+ * Gets the page size in PostScript points (1/72th of an inch).
*
* @return The page size.
*/
@@ -278,7 +265,7 @@ public final class PdfDocument {
}
/**
- * Get the content size in pixels.
+ * Get the content size in PostScript points (1/72th of an inch).
*
* @return The content size.
*/
@@ -307,15 +294,6 @@ public final class PdfDocument {
}
/**
- * Gets the density of the page in DPI.
- *
- * @return The density.
- */
- public int getDesity() {
- return mDensity;
- }
-
- /**
* Builder for creating a {@link PageInfo}.
*/
public static final class Builder {
@@ -324,11 +302,10 @@ public final class PdfDocument {
/**
* Creates a new builder with the mandatory page info attributes.
*
- * @param pageSize The page size in points, <strong>not</strong> dips.
+ * @param pageSize The page size in PostScript (1/72th of an inch).
* @param pageNumber The page number.
- * @param density The page density in DPI.
*/
- public Builder(Rect pageSize, int pageNumber, int density) {
+ public Builder(Rect pageSize, int pageNumber) {
if (pageSize.width() == 0 || pageSize.height() == 0) {
throw new IllegalArgumentException("page width and height" +
" must be greater than zero!");
@@ -336,16 +313,12 @@ public final class PdfDocument {
if (pageNumber < 0) {
throw new IllegalArgumentException("pageNumber cannot be less than zero!");
}
- if (density <= 0) {
- throw new IllegalArgumentException("density must be greater than zero!");
- }
mPageInfo.mPageSize = pageSize;
mPageInfo.mPageNumber = pageNumber;
- mPageInfo.mDensity = density;
}
/**
- * Sets the content size in pixels.
+ * Sets the content size in PostScript point (1/72th of an inch).
*
* @param contentSize The content size.
*/
diff --git a/core/java/android/print/pdf/PrintedPdfDocument.java b/core/java/android/print/pdf/PrintedPdfDocument.java
new file mode 100644
index 0000000..bee17ef
--- /dev/null
+++ b/core/java/android/print/pdf/PrintedPdfDocument.java
@@ -0,0 +1,165 @@
+/*
+ * 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.pdf;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.pdf.PdfDocument;
+import android.print.pdf.PdfDocument.Page;
+import android.print.pdf.PdfDocument.PageInfo;
+
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * This class is a helper for printing content to a different media
+ * size. This class is responsible for computing a correct page size
+ * given some print constraints, i.e. {@link PrintAttributes}. It is
+ * an adapter around a {@link PdfDocument}.
+ */
+public final class PrintedPdfDocument {
+ private static final int MILS_PER_INCH = 1000;
+ private static final int POINTS_IN_INCH = 72;
+
+ private final PdfDocument mDocument = PdfDocument.open();
+ private final Rect mPageSize = new Rect();
+ private final Rect mContentSize = new Rect();
+
+ /**
+ * Opens a new document. The document pages are computed based on
+ * the passes in {@link PrintAttributes}.
+ * <p>
+ * <strong>Note:</strong> You must close the document after you are
+ * done by calling {@link #close()}
+ * </p>
+ *
+ * @param context Context instance for accessing resources.
+ * @param attributes The print attributes.
+ * @return The document.
+ *
+ * @see #close()
+ */
+ public static PrintedPdfDocument open(Context context, PrintAttributes attributes) {
+ return new PrintedPdfDocument(context, attributes);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context Context instance for accessing resources and services.
+ * @param attributes The {@link PrintAttributes} to user.
+ */
+ private PrintedPdfDocument(Context context, PrintAttributes attributes) {
+ MediaSize mediaSize = attributes.getMediaSize();
+
+ // Compute the size of the target canvas from the attributes.
+ final int pageWidth = (int) (((float) mediaSize.getWidthMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int pageHeight = (int) (((float) mediaSize.getHeightMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ mPageSize.set(0, 0, pageWidth, pageHeight);
+
+ // Compute the content size from the attributes.
+ Margins margins = attributes.getMargins();
+ final int marginLeft = (int) (((float) margins.getLeftMils() /MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int marginTop = (int) (((float) margins.getTopMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int marginRight = (int) (((float) margins.getRightMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ final int marginBottom = (int) (((float) margins.getBottomMils() / MILS_PER_INCH)
+ * POINTS_IN_INCH);
+ mContentSize.set(mPageSize.left + marginLeft, mPageSize.top + marginTop,
+ mPageSize.right - marginRight, mPageSize.bottom - marginBottom);
+ }
+
+ /**
+ * Starts a page using a page size computed from the print attributes
+ * passed in {@link #open(Context, PrintAttributes)} and the given page
+ * number to create appropriate {@link PageInfo}.
+ * <p>
+ * After the page is created you can draw arbitrary content on the page's
+ * canvas which you can get by calling {@link Page#getCanvas() Page.getCanvas()}.
+ * After you are done drawing the content you should finish the page by calling
+ * {@link #finishPage(Page)}. After the page is finished you should no longer
+ * access the page or its canvas.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Do not call this method after {@link #close()}.
+ * </p>
+ *
+ * @param pageNumber The page number.
+ * @return A blank page.
+ *
+ * @see #finishPage(Page)
+ */
+ public Page startPage(int pageNumber) {
+ PageInfo pageInfo = new PageInfo
+ .Builder(mPageSize, 0)
+ .setContentSize(mContentSize)
+ .create();
+ Page page = mDocument.startPage(pageInfo);
+ return page;
+ }
+
+ /**
+ * Finishes a started page. You should always finish the last started page.
+ * <p>
+ * <strong>Note:</strong> Do not call this method after {@link #close()}.
+ * </p>
+ *
+ * @param page The page.
+ *
+ * @see #startPage(int)
+ */
+ public void finishPage(Page page) {
+ mDocument.finishPage(page);
+ }
+
+ /**
+ * Writes the document to an output stream.
+ * <p>
+ * <strong>Note:</strong> Do not call this method after {@link #close()}.
+ * </p>
+ *
+ * @param out The output stream.
+ */
+ public void writeTo(OutputStream out) {
+ mDocument.writeTo(out);
+ }
+
+ /**
+ * Gets the pages of the document.
+ *
+ * @return The pages.
+ */
+ public List<PageInfo> getPages() {
+ return mDocument.getPages();
+ }
+
+ /**
+ * Closes this document. This method should be called after you
+ * are done working with the document. After this call the document
+ * is considered closed and none of its methods should be called.
+ */
+ public void close() {
+ mDocument.close();
+ }
+}
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index 2cee1d8..ee36619 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -33,6 +33,8 @@ oneway interface IPrintService {
void createPrinterDiscoverySession();
void startPrinterDiscovery(in List<PrinterId> priorityList);
void stopPrinterDiscovery();
- void requestPrinterUpdate(in PrinterId printerId);
+ void validatePrinters(in List<PrinterId> printerIds);
+ void startPrinterStateTracking(in PrinterId printerId);
+ void stopPrinterStateTracking(in PrinterId printerId);
void destroyPrinterDiscoverySession();
}
diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java
index 7437dc5..8292cfb 100644
--- a/core/java/android/printservice/PrintDocument.java
+++ b/core/java/android/printservice/PrintDocument.java
@@ -21,12 +21,15 @@ import android.os.RemoteException;
import android.print.PrintDocumentInfo;
import android.util.Log;
-import java.io.FileDescriptor;
import java.io.IOException;
/**
* This class represents a printed document from the perspective of a print
* service. It exposes APIs to query the document and obtain its data.
+ * <p>
+ * <strong>Note: </strong> All methods of this class must be executed on the
+ * main application thread.
+ * </p>
*/
public final class PrintDocument {
@@ -51,6 +54,7 @@ public final class PrintDocument {
* @return The document info.
*/
public PrintDocumentInfo getInfo() {
+ PrintService.throwIfNotCalledOnMainThread();
return mInfo;
}
@@ -64,7 +68,8 @@ public final class PrintDocument {
*
* @return A file descriptor for reading the data.
*/
- public FileDescriptor getData() {
+ public ParcelFileDescriptor getData() {
+ PrintService.throwIfNotCalledOnMainThread();
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
try {
@@ -72,7 +77,7 @@ public final class PrintDocument {
source = fds[0];
sink = fds[1];
mPrintServiceClient.writePrintJobData(sink, mPrintJobId);
- return source.getFileDescriptor();
+ return source;
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error calling getting print job data!", ioe);
} catch (RemoteException re) {
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index d2fbef2..8bae9d6 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -18,6 +18,7 @@ package android.printservice;
import android.os.RemoteException;
import android.print.PrintJobInfo;
+import android.text.TextUtils;
import android.util.Log;
/**
@@ -123,6 +124,21 @@ public final class PrintJob {
}
/**
+ * Gets whether this print job is blocked. Such a print job is halted
+ * due to an abnormal condition and can be started or canceled or failed.
+ *
+ * @return Whether the print job is blocked.
+ *
+ * @see #start()
+ * @see #cancel()
+ * @see #fail(CharSequence)
+ */
+ public boolean isBlocked() {
+ PrintService.throwIfNotCalledOnMainThread();
+ return getInfo().getState() == PrintJobInfo.STATE_BLOCKED;
+ }
+
+ /**
* Gets whether this print job is completed. Such a print job
* is successfully printed. This is a final state.
*
@@ -163,21 +179,49 @@ public final class PrintJob {
/**
* Starts the print job. You should call this method if {@link
- * #isQueued()} returns true and you started printing.
+ * #isQueued()} or {@link #isBlocked()} returns true and you started
+ * resumed printing.
*
- * @return Whether the job as started.
+ * @return Whether the job was started.
*
* @see #isQueued()
+ * @see #isBlocked()
*/
public boolean start() {
PrintService.throwIfNotCalledOnMainThread();
- if (isQueued()) {
+ final int state = getInfo().getState();
+ if (state == PrintJobInfo.STATE_QUEUED
+ || state == PrintJobInfo.STATE_BLOCKED) {
return setState(PrintJobInfo.STATE_STARTED, null);
}
return false;
}
/**
+ * Blocks the print job. You should call this method if {@link
+ * #isStarted()} or {@link #isBlocked()} returns true and you need
+ * to block the print job. For example, the user has to add some
+ * paper to continue printing. To resume the print job call {@link
+ * #start()}.
+ *
+ * @return Whether the job was blocked.
+ *
+ * @see #isStarted()
+ * @see #isBlocked()
+ */
+ public boolean block(String reason) {
+ PrintService.throwIfNotCalledOnMainThread();
+ PrintJobInfo info = getInfo();
+ final int state = info.getState();
+ if (state == PrintJobInfo.STATE_STARTED
+ || (state == PrintJobInfo.STATE_BLOCKED
+ && !TextUtils.equals(info.getStateReason(), reason))) {
+ return setState(PrintJobInfo.STATE_BLOCKED, reason);
+ }
+ return false;
+ }
+
+ /**
* Completes the print job. You should call this method if {@link
* #isStarted()} returns true and you are done printing.
*
@@ -195,8 +239,8 @@ public final class PrintJob {
/**
* Fails the print job. You should call this method if {@link
- * #isQueued()} or {@link #isStarted()} returns true you failed
- * while printing.
+ * #isQueued()} or {@link #isStarted()} or {@link #isBlocked()}
+ * returns true you failed while printing.
*
* @param error The human readable, short, and translated reason
* for the failure.
@@ -204,10 +248,11 @@ public final class PrintJob {
*
* @see #isQueued()
* @see #isStarted()
+ * @see #isBlocked()
*/
public boolean fail(String error) {
PrintService.throwIfNotCalledOnMainThread();
- if (isQueued() || isStarted()) {
+ if (!isInImmutableState()) {
return setState(PrintJobInfo.STATE_FAILED, error);
}
return false;
@@ -215,18 +260,19 @@ public final class PrintJob {
/**
* Cancels the print job. You should call this method if {@link
- * #isQueued()} or {@link #isStarted()} returns true and you canceled
- * the print job as a response to a call to {@link
- * PrintService#onRequestCancelPrintJob(PrintJob)}.
+ * #isQueued()} or {@link #isStarted() or #isBlocked()} returns
+ * true and you canceled the print job as a response to a call to
+ * {@link PrintService#onRequestCancelPrintJob(PrintJob)}.
*
* @return Whether the job is canceled.
*
* @see #isStarted()
* @see #isQueued()
+ * @see #isBlocked()
*/
public boolean cancel() {
PrintService.throwIfNotCalledOnMainThread();
- if (isQueued() || isStarted()) {
+ if (!isInImmutableState()) {
return setState(PrintJobInfo.STATE_CANCELED, null);
}
return false;
@@ -277,7 +323,8 @@ public final class PrintJob {
private boolean isInImmutableState() {
final int state = mCachedInfo.getState();
return state == PrintJobInfo.STATE_COMPLETED
- || state == PrintJobInfo.STATE_CANCELED;
+ || state == PrintJobInfo.STATE_CANCELED
+ || state == PrintJobInfo.STATE_FAILED;
}
private boolean setState(int state, String error) {
@@ -287,7 +334,7 @@ public final class PrintJob {
// we may not be able to re-fetch it later if the job gets
// removed from the spooler as a result of the state change.
mCachedInfo.setState(state);
- mCachedInfo.setFailureReason(error);
+ mCachedInfo.setStateReason(error);
return true;
}
} catch (RemoteException re) {
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index f6c0a9a..96552af 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -314,8 +314,20 @@ public abstract class PrintService extends Service {
}
@Override
- public void requestPrinterUpdate(PrinterId printerId) {
- mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_PRINTER_UPDATE,
+ public void validatePrinters(List<PrinterId> printerIds) {
+ mHandler.obtainMessage(ServiceHandler.MSG_VALIDATE_PRINTERS,
+ printerIds).sendToTarget();
+ }
+
+ @Override
+ public void startPrinterStateTracking(PrinterId printerId) {
+ mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_STATE_TRACKING,
+ printerId).sendToTarget();
+ }
+
+ @Override
+ public void stopPrinterStateTracking(PrinterId printerId) {
+ mHandler.obtainMessage(ServiceHandler.MSG_STOP_PRINTER_STATE_TRACKING,
printerId).sendToTarget();
}
@@ -344,10 +356,12 @@ public abstract class PrintService extends Service {
public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
public static final int MSG_START_PRINTER_DISCOVERY = 3;
public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
- public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
- public static final int MSG_ON_PRINTJOB_QUEUED = 6;
- public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 7;
- public static final int MSG_SET_CLEINT = 8;
+ public static final int MSG_VALIDATE_PRINTERS = 5;
+ public static final int MSG_START_PRINTER_STATE_TRACKING = 6;
+ public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7;
+ public static final int MSG_ON_PRINTJOB_QUEUED = 8;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 9;
+ public static final int MSG_SET_CLEINT = 10;
public ServiceHandler(Looper looper) {
super(looper, null, true);
@@ -391,10 +405,24 @@ public abstract class PrintService extends Service {
}
} break;
- case MSG_REQUEST_PRINTER_UPDATE: {
+ case MSG_VALIDATE_PRINTERS: {
+ if (mDiscoverySession != null) {
+ List<PrinterId> printerIds = (List<PrinterId>) message.obj;
+ mDiscoverySession.validatePrinters(printerIds);
+ }
+ } break;
+
+ case MSG_START_PRINTER_STATE_TRACKING: {
+ if (mDiscoverySession != null) {
+ PrinterId printerId = (PrinterId) message.obj;
+ mDiscoverySession.startPrinterStateTracking(printerId);
+ }
+ } break;
+
+ case MSG_STOP_PRINTER_STATE_TRACKING: {
if (mDiscoverySession != null) {
PrinterId printerId = (PrinterId) message.obj;
- mDiscoverySession.requestPrinterUpdate(printerId);
+ mDiscoverySession.stopPrinterStateTracking(printerId);
}
} break;
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index 8b959a6..6464cc1 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -53,15 +53,23 @@ import java.util.List;
* session. Printers are <strong>not</strong> persisted across sessions.
* </p>
* <p>
- * The system will make a call to
- * {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you
- * need to update a given printer. It is possible that you add a printer without
+ * The system will make a call to {@link #onValidatePrinters(List)} if you
+ * need to update some printers. It is possible that you add a printer without
* specifying its capabilities. This enables you to avoid querying all discovered
* printers for their capabilities, rather querying the capabilities of a printer
* only if necessary. For example, the system will request that you update a printer
- * if it gets selected by the user. If you did not report the printer capabilities
- * when adding it, you must do so after the system requests a printer update.
- * Otherwise, the printer will be ignored.
+ * if it gets selected by the user. When validating printers you do not need to
+ * provide the printers' capabilities but may do so.
+ * </p>
+ * <p>
+ * If the system is interested in being constantly updated for the state of a
+ * printer you will receive a call to {@link #onStartPrinterStateTracking(PrinterId)}
+ * after which you will have to do a best effort to keep the system updated for
+ * changes in the printer state and capabilities. You also <strong>must</strong>
+ * update the printer capabilities if you did not provide them when adding it, or
+ * the printer will be ignored. When the system is no longer interested in getting
+ * updates for a printer you will receive a call to {@link #onStopPrinterStateTracking(
+ * PrinterId)}.
* </p>
* <p>
* <strong>Note: </strong> All callbacks in this class are executed on the main
@@ -72,7 +80,7 @@ import java.util.List;
public abstract class PrinterDiscoverySession {
private static final String LOG_TAG = "PrinterDiscoverySession";
- private static final int MAX_ITEMS_PER_CALLBACK = 100;
+ private static final int MAX_ITEMS_PER_CALLBACK = 50;
private static int sIdCounter = 0;
@@ -115,7 +123,7 @@ public abstract class PrinterDiscoverySession {
* the printer that was added but not removed.
* <p>
* <strong>Note: </strong> Calls to this method after the session is
- * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
+ * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @return The printers.
@@ -139,7 +147,7 @@ public abstract class PrinterDiscoverySession {
* times during the life of this session. Duplicates will be ignored.
* <p>
* <strong>Note: </strong> Calls to this method after the session is
- * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
+ * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printers The printers to add.
@@ -218,7 +226,7 @@ public abstract class PrinterDiscoverySession {
* call this method multiple times during the lifetime of this session.
* <p>
* <strong>Note: </strong> Calls to this method after the session is
- * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
+ * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printerIds The ids of the removed printers.
@@ -293,7 +301,7 @@ public abstract class PrinterDiscoverySession {
* during the lifetime of this session.
* <p>
* <strong>Note: </strong> Calls to this method after the session is
- * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
+ * destroyed, that is after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printers The printers to update.
@@ -441,7 +449,9 @@ public abstract class PrinterDiscoverySession {
* <p>
* <strong>Note: </strong>You are also given a list of printers whose availability
* has to be checked first. For example, these printers could be the user's favorite
- * ones, therefore they have to be verified first.
+ * ones, therefore they have to be verified first. You do <strong>not need</strong>
+ * to provide the capabilities of the printers, rather verify whether they exist
+ * similarly to {@link #onValidatePrinters(List)}.
* </p>
*
* @param priorityList The list of printers to validate first. Never null.
@@ -463,9 +473,28 @@ public abstract class PrinterDiscoverySession {
public abstract void onStopPrinterDiscovery();
/**
- * Requests that you update a printer. You are responsible for updating
- * the printer by also reporting its capabilities via calling {@link
- * #updatePrinters(List)}.
+ * Callback asking you to validate that the given printers are valid, that
+ * is they exist. You are responsible for checking whether these printers
+ * exist and for the ones that do exist notify the system via calling
+ * {@link #updatePrinters(List)}.
+ * <p>
+ * <strong>Note: </strong> You are <strong>not required</strong> to provide
+ * the printer capabilities when updating the printers that do exist.
+ * <p>
+ *
+ * @param printerIds The printers to validate.
+ *
+ * @see #updatePrinters(List)
+ * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
+ * PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
+ */
+ public abstract void onValidatePrinters(List<PrinterId> printerIds);
+
+ /**
+ * Callback asking you to start tracking the state of a printer. Tracking
+ * the state means that you should do a best effort to observe the state
+ * of this printer and notify the system if that state changes via calling
+ * {@link #updatePrinters(List)}.
* <p>
* <strong>Note: </strong> A printer can be initially added without its
* capabilities to avoid polling printers that the user will not select.
@@ -473,18 +502,33 @@ public abstract class PrinterDiscoverySession {
* printer <strong>including</strong> its capabilities. Otherwise, the
* printer will be ignored.
* <p>
- * A scenario when you may be requested to update a printer is if the user
- * selects it and the system has to present print options UI based on the
- * printer's capabilities.
+ * <p>
+ * A scenario when you may be requested to track a printer's state is if
+ * the user selects that printer and the system has to present print
+ * options UI based on the printer's capabilities. In this case the user
+ * should be promptly informed if, for example, the printer becomes
+ * unavailable.
* </p>
*
- * @param printerId The printer id.
+ * @param printerId The printer to start tracking.
*
+ * @see #onStopPrinterStateTracking(PrinterId)
* @see #updatePrinters(List)
* @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
* PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
*/
- public abstract void onRequestPrinterUpdate(PrinterId printerId);
+ public abstract void onStartPrinterStateTracking(PrinterId printerId);
+
+ /**
+ * Callback asking you to stop tracking the state of a printer. The passed
+ * in printer id is the one for which you received a call to {@link
+ * #onStartPrinterStateTracking(PrinterId)}.
+ *
+ * @param printerId The printer to stop tracking.
+ *
+ * @see #onStartPrinterStateTracking(PrinterId)
+ */
+ public abstract void onStopPrinterStateTracking(PrinterId printerId);
/**
* Notifies you that the session is destroyed. After this callback is invoked
@@ -538,9 +582,21 @@ public abstract class PrinterDiscoverySession {
}
}
- void requestPrinterUpdate(PrinterId printerId) {
- if (!mIsDestroyed) {
- onRequestPrinterUpdate(printerId);
+ void validatePrinters(List<PrinterId> printerIds) {
+ if (!mIsDestroyed && mObserver != null) {
+ onValidatePrinters(printerIds);
+ }
+ }
+
+ void startPrinterStateTracking(PrinterId printerId) {
+ if (!mIsDestroyed && mObserver != null) {
+ onStartPrinterStateTracking(printerId);
+ }
+ }
+
+ void stopPrinterStateTracking(PrinterId printerId) {
+ if (!mIsDestroyed && mObserver != null) {
+ onStopPrinterStateTracking(printerId);
}
}
diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java
index f3267ee..724d76d 100644
--- a/core/java/android/provider/AlarmClock.java
+++ b/core/java/android/provider/AlarmClock.java
@@ -36,10 +36,15 @@ public final class AlarmClock {
* <p>
* Activates an existing alarm or creates a new one.
* </p><p>
- * This action requests an alarm to be set for a given time of day. If an alarm already
- * exists for this time, an implementation may use it rather than create a new one. If no time
- * of day is specified, the implementation should start an activity that is capable of setting
- * an alarm (SKIP_UI is ignored in this case). This action always enables the alarm.
+ * This action requests an alarm to be set for a given time of day. If no time of day is
+ * specified, an implementation should start an activity that is capable of setting an alarm
+ * ({@link #EXTRA_SKIP_UI} is ignored in this case). If a time of day is specified, and
+ * {@link #EXTRA_SKIP_UI} is {@code true}, and the alarm is not repeating, the implementation
+ * should remove this alarm after it has been dismissed. If an identical alarm exists matching
+ * all parameters, the implementation may re-use it instead of creating a new one (in this case,
+ * the alarm should not be removed after dismissal).
+ *
+ * This action always enables the alarm.
* </p>
* <h3>Request parameters</h3>
* <ul>
@@ -52,8 +57,6 @@ public final class AlarmClock {
* vibrator for this alarm.
* <li>{@link #EXTRA_SKIP_UI} <em>(optional)</em>: Whether or not to display an activity for
* setting this alarm.
- * <li>{@link #EXTRA_DELETE_AFTER_USE} <em>(optional)</em>: Whether or not to delete this
- * alarm after it is dismissed.
* </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -65,13 +68,14 @@ public final class AlarmClock {
* Activates an existing timer or creates a new one.
* </p><p>
* This action requests a timer to be started for a specific {@link #EXTRA_LENGTH length} of
- * time. If a timer already exists for this {@link #EXTRA_LENGTH length}, an implementation may
- * use it rather than create a new one. If no {@link #EXTRA_LENGTH length} is specified, the
- * implementation should start an activity that is capable of setting a timer
- * ({@link #EXTRA_SKIP_UI} is ignored in this case).
- * </p><p>
- * An existing timer should only be used if it matches the provided extras and is not currently
- * in use.
+ * time. If no {@link #EXTRA_LENGTH length} is specified, the implementation should start an
+ * activity that is capable of setting a timer ({@link #EXTRA_SKIP_UI} is ignored in this case).
+ * If a {@link #EXTRA_LENGTH length} is specified, and {@link #EXTRA_SKIP_UI} is {@code true},
+ * the implementation should remove this timer after it has been dismissed. If an identical,
+ * unused timer exists matching both parameters, an implementation may re-use it instead of
+ * creating a new one (in this case, the timer should not be removed after dismissal).
+ *
+ * This action always starts the timer.
* </p>
*
* <h3>Request parameters</h3>
@@ -80,14 +84,21 @@ public final class AlarmClock {
* <li>{@link #EXTRA_MESSAGE} <em>(optional)</em>: A custom message for the timer.
* <li>{@link #EXTRA_SKIP_UI} <em>(optional)</em>: Whether or not to display an activity for
* setting this timer.
- * <li>{@link #EXTRA_DELETE_AFTER_USE} <em>(optional)</em>: Whether or not to delete this
- * timer after it is dismissed.
* </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SET_TIMER = "android.intent.action.SET_TIMER";
/**
+ * Activity Action: Show the alarms.
+ * <p>
+ * This action opens the alarms page.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS";
+
+ /**
* Bundle extra: Weekdays for repeating alarm.
* <p>
* Used by {@link #ACTION_SET_ALARM}.
@@ -103,30 +114,10 @@ public final class AlarmClock {
* <li> {@link java.util.Calendar#FRIDAY},
* <li> {@link java.util.Calendar#SATURDAY}
* </ul>
- * <p>
- * Note: If this extra is provided, {@link #EXTRA_DELETE_AFTER_USE} is ignored.
- * </p>
*/
public static final String EXTRA_DAYS = "android.intent.extra.alarm.DAYS";
/**
- * Bundle extra: Whether or not to delete this alarm/timer after it's dismissed.
- * <p>
- * Used by {@link #ACTION_SET_ALARM} and {@link #ACTION_SET_TIMER}.
- * </p><p>
- * If this value is true, the alarm/timer used by this action should be deleted after it's been
- * dismissed. The alarm/timer should only be removed if was actually created by the action. If
- * an existing alarm/timer was used, it should not be deleted after it's dismissed.
- * </p><p>
- * The value is a {@link Boolean}.
- * </p>
- *
- * @see #ACTION_SET_ALARM
- * @see #ACTION_SET_TIMER
- */
- public static final String EXTRA_DELETE_AFTER_USE = "android.intent.extra.alarm.DELETE_AFTER_USE";
-
- /**
* Bundle extra: The hour of the alarm.
* <p>
* Used by {@link #ACTION_SET_ALARM}.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 65c9220..f445fd5 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,9 +19,7 @@ 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;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -33,6 +31,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.util.Log;
@@ -51,448 +50,532 @@ import java.util.List;
/**
* 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.
+ * To create a document provider, extend {@link DocumentsProvider}, which
+ * provides a foundational implementation of this contract.
*
- * @see Intent#ACTION_OPEN_DOCUMENT
- * @see Intent#ACTION_CREATE_DOCUMENT
+ * @see DocumentsProvider
*/
public final class DocumentsContract {
private static final String TAG = "Documents";
- // content://com.example/roots/
- // content://com.example/roots/sdcard/
- // content://com.example/roots/sdcard/docs/0/
- // content://com.example/roots/sdcard/docs/0/contents/
- // content://com.example/roots/sdcard/docs/0/search/?query=pony
+ // content://com.example/root/
+ // content://com.example/root/sdcard/
+ // content://com.example/root/sdcard/recent/
+ // content://com.example/document/12/
+ // content://com.example/document/12/children/
+ // content://com.example/document/12/search/?query=pony
+
+ private DocumentsContract() {
+ }
/** {@hide} */
public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER";
/** {@hide} */
- public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED";
+ public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
/**
- * Constants for individual documents.
+ * Constants related to a document, including {@link Cursor} columns names
+ * and flags.
+ * <p>
+ * A document can be either an openable file (with a specific MIME type), or
+ * a directory containing additional documents (with the
+ * {@link #MIME_TYPE_DIR} MIME type).
+ * <p>
+ * All columns are <em>read-only</em> to client applications.
*/
- public static class Documents {
- private Documents() {
+ public final static class Document {
+ private Document() {
}
/**
- * MIME type of a document which is a directory that may contain additional
- * documents.
+ * Unique ID of a document. This ID is both provided by and interpreted
+ * by a {@link DocumentsProvider}, and should be treated as an opaque
+ * value by client applications.
+ * <p>
+ * Each document must have a unique ID within a provider, but that
+ * single document may be included as a child of multiple directories.
+ * <p>
+ * A provider must always return durable IDs, since they will be used to
+ * issue long-term Uri permission grants when an application interacts
+ * with {@link Intent#ACTION_OPEN_DOCUMENT} and
+ * {@link Intent#ACTION_CREATE_DOCUMENT}.
+ * <p>
+ * Type: STRING
+ */
+ public static final String COLUMN_DOCUMENT_ID = "document_id";
+
+ /**
+ * Concrete MIME type of a document. For example, "image/png" or
+ * "application/pdf" for openable files. A document can also be a
+ * directory containing additional documents, which is represented with
+ * the {@link #MIME_TYPE_DIR} MIME type.
+ * <p>
+ * Type: STRING
*
- * @see #buildContentsUri(String, String, String)
+ * @see #MIME_TYPE_DIR
*/
- public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc";
+ public static final String COLUMN_MIME_TYPE = "mime_type";
/**
- * {@link DocumentColumns#DOC_ID} value representing the root directory of a
- * documents root.
+ * Display name of a document, used as the primary title displayed to a
+ * user.
+ * <p>
+ * Type: STRING
*/
- public static final String DOC_ID_ROOT = "0";
+ public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
/**
- * Flag indicating that a document is a directory that supports creation of
- * new files within it.
+ * Summary of a document, which may be shown to a user. The summary may
+ * be {@code null}.
+ * <p>
+ * Type: STRING
+ */
+ public static final String COLUMN_SUMMARY = "summary";
+
+ /**
+ * Timestamp when a document was last modified, in milliseconds since
+ * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
+ * {@link DocumentsProvider} can update this field using events from
+ * {@link OnCloseListener} or other reliable
+ * {@link ParcelFileDescriptor} transports.
+ * <p>
+ * Type: INTEGER (long)
*
- * @see DocumentColumns#FLAGS
- * @see #createDocument(ContentResolver, Uri, String, String)
+ * @see System#currentTimeMillis()
*/
- public static final int FLAG_SUPPORTS_CREATE = 1;
+ public static final String COLUMN_LAST_MODIFIED = "last_modified";
+
+ /**
+ * Specific icon resource ID for a document, or {@code null} to use
+ * platform default icon based on {@link #COLUMN_MIME_TYPE}.
+ * <p>
+ * Type: INTEGER (int)
+ */
+ public static final String COLUMN_ICON = "icon";
/**
- * Flag indicating that a document is renamable.
+ * Flags that apply to a document.
+ * <p>
+ * Type: INTEGER (int)
*
- * @see DocumentColumns#FLAGS
- * @see #renameDocument(ContentResolver, Uri, String)
+ * @see #FLAG_SUPPORTS_WRITE
+ * @see #FLAG_SUPPORTS_DELETE
+ * @see #FLAG_SUPPORTS_THUMBNAIL
+ * @see #FLAG_DIR_PREFERS_GRID
+ * @see #FLAG_DIR_SUPPORTS_CREATE
+ * @see #FLAG_DIR_SUPPORTS_SEARCH
*/
- public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
+ public static final String COLUMN_FLAGS = "flags";
/**
- * Flag indicating that a document is deletable.
+ * Size of a document, in bytes, or {@code null} if unknown.
+ * <p>
+ * Type: INTEGER (long)
+ */
+ public static final String COLUMN_SIZE = OpenableColumns.SIZE;
+
+ /**
+ * MIME type of a document which is a directory that may contain
+ * additional documents.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_MIME_TYPE
*/
- public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
+ public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
/**
* Flag indicating that a document can be represented as a thumbnail.
*
- * @see DocumentColumns#FLAGS
- * @see #getThumbnail(ContentResolver, Uri, Point)
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
+ * Point, CancellationSignal)
+ * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+ * android.os.CancellationSignal)
*/
- public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
+ public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
/**
- * Flag indicating that a document is a directory that supports search.
+ * Flag indicating that a document supports writing.
+ * <p>
+ * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
+ * the calling application is granted both
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
+ * writability of a document may change over time, for example due to
+ * remote access changes. This flag indicates that a document client can
+ * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_FLAGS
*/
- public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
+ public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
/**
- * Flag indicating that a document is writable.
+ * Flag indicating that a document is deletable.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
+ * @see DocumentsProvider#deleteDocument(String)
*/
- public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
+ public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
/**
- * Flag indicating that a document is a directory that prefers its contents
- * be shown in a larger format grid. Usually suitable when a directory
- * contains mostly pictures.
+ * Flag indicating that a document is a directory that supports creation
+ * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
+ * {@link #MIME_TYPE_DIR}.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
+ * String)
+ * @see DocumentsProvider#createDocument(String, String, String)
*/
- public static final int FLAG_PREFERS_GRID = 1 << 6;
- }
-
- /**
- * Optimal dimensions for a document thumbnail request, stored as a
- * {@link Point} object. This is only a hint, and the returned thumbnail may
- * have different dimensions.
- *
- * @see ContentProvider#openTypedAssetFile(Uri, String, Bundle)
- */
- public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
-
- /**
- * Extra boolean flag included in a directory {@link Cursor#getExtras()}
- * 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";
+ public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
- /**
- * Extra boolean flag included in a {@link Cursor#respond(Bundle)} call to a
- * directory to request that additional data should be fetched. When
- * requested data is ready, the provider should send a change notification
- * to cause a requery.
- *
- * @see Cursor#respond(Bundle)
- * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
- * boolean)
- */
- public static final String EXTRA_REQUEST_MORE = "request_more";
-
- private static final String PATH_ROOTS = "roots";
- private static final String PATH_DOCS = "docs";
- private static final String PATH_CONTENTS = "contents";
- private static final String PATH_SEARCH = "search";
-
- private static final String PARAM_QUERY = "query";
- private static final String PARAM_LOCAL_ONLY = "localOnly";
-
- /**
- * 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
- * document provider.
- */
- public static Uri buildDocumentUri(String authority, String rootId, String docId) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
- .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
- .build();
- }
-
- /**
- * 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)
- .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
- .appendPath(PATH_CONTENTS).build();
- }
-
- /**
- * 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)
- .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId)
- .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);
- }
+ /**
+ * Flag indicating that a directory supports search. Only valid when
+ * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsProvider#querySearchDocuments(String, String,
+ * String[])
+ */
+ public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4;
- /**
- * 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) {
- throw new IllegalArgumentException("Not a root: " + documentUri);
- }
- if (!PATH_ROOTS.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a root: " + documentUri);
- }
- return paths.get(1);
+ /**
+ * Flag indicating that a directory prefers its contents be shown in a
+ * larger format grid. Usually suitable when a directory contains mostly
+ * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
+ * {@link #MIME_TYPE_DIR}.
+ *
+ * @see #COLUMN_FLAGS
+ */
+ public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;
}
/**
- * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
+ * Constants related to a root of documents, including {@link Cursor}
+ * columns names and flags.
+ * <p>
+ * All columns are <em>read-only</em> to client applications.
*/
- public static String getDocId(Uri documentUri) {
- final List<String> paths = documentUri.getPathSegments();
- if (paths.size() < 4) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
- }
- if (!PATH_ROOTS.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
+ public final static class Root {
+ private Root() {
}
- if (!PATH_DOCS.equals(paths.get(2))) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
- }
- return paths.get(3);
- }
- /**
- * 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 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()
- .appendQueryParameter(PARAM_LOCAL_ONLY, String.valueOf(true)).build();
- }
-
- /**
- * 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);
- }
-
- /**
- * Standard columns for document queries. Document providers <em>must</em>
- * support at least these columns when queried.
- *
- * @see DocumentsContract#buildDocumentUri(String, String, String)
- * @see DocumentsContract#buildContentsUri(String, String, String)
- * @see DocumentsContract#buildSearchUri(String, String, String, String)
- */
- public interface DocumentColumns extends OpenableColumns {
/**
- * The ID for a document under a storage backend root. Values
- * <em>must</em> never change once returned. This field is read-only to
- * document clients.
+ * Unique ID of a root. This ID is both provided by and interpreted by a
+ * {@link DocumentsProvider}, and should be treated as an opaque value
+ * by client applications.
* <p>
* Type: STRING
*/
- public static final String DOC_ID = "doc_id";
+ public static final String COLUMN_ROOT_ID = "root_id";
/**
- * 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. This field is read-only to
- * document clients.
+ * Type of a root, used for clustering when presenting multiple roots to
+ * a user.
* <p>
- * Type: STRING
+ * Type: INTEGER (int)
*
- * @see Documents#MIME_TYPE_DIR
+ * @see #ROOT_TYPE_SERVICE
+ * @see #ROOT_TYPE_SHORTCUT
+ * @see #ROOT_TYPE_DEVICE
*/
- public static final String MIME_TYPE = "mime_type";
+ public static final String COLUMN_ROOT_TYPE = "root_type";
/**
- * 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. Document providers can update this field using events from
- * {@link OnCloseListener} or other reliable
- * {@link ParcelFileDescriptor} transport.
+ * Flags that apply to a root.
* <p>
- * Type: INTEGER (long)
+ * Type: INTEGER (int)
*
- * @see System#currentTimeMillis()
+ * @see #FLAG_LOCAL_ONLY
+ * @see #FLAG_SUPPORTS_CREATE
+ * @see #FLAG_ADVANCED
+ * @see #FLAG_PROVIDES_AUDIO
+ * @see #FLAG_PROVIDES_IMAGES
+ * @see #FLAG_PROVIDES_VIDEO
*/
- public static final String LAST_MODIFIED = "last_modified";
+ public static final String COLUMN_FLAGS = "flags";
/**
- * Flags that apply to a specific document. This field is read-only to
- * document clients.
+ * Icon resource ID for a root.
* <p>
* Type: INTEGER (int)
*/
- public static final String FLAGS = "flags";
+ public static final String COLUMN_ICON = "icon";
/**
- * Summary for this document, or {@code null} to omit. This field is
- * read-only to document clients.
+ * Title for a root, which will be shown to a user.
* <p>
* Type: STRING
*/
- public static final String SUMMARY = "summary";
- }
+ public static final String COLUMN_TITLE = "title";
- /**
- * Constants for individual document roots.
- */
- public static class Roots {
- private Roots() {
- }
+ /**
+ * Summary for this root, which may be shown to a user. The summary may
+ * be {@code null}.
+ * <p>
+ * Type: STRING
+ */
+ public static final String COLUMN_SUMMARY = "summary";
+
+ /**
+ * Document which is a directory that represents the top directory of
+ * this root.
+ * <p>
+ * Type: STRING
+ *
+ * @see Document#COLUMN_DOCUMENT_ID
+ */
+ public static final String COLUMN_DOCUMENT_ID = "document_id";
- public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/root";
- public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root";
+ /**
+ * Number of bytes available in this root, or {@code null} if unknown or
+ * unbounded.
+ * <p>
+ * Type: INTEGER (long)
+ */
+ public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
/**
- * Root that represents a storage service, such as a cloud-based
+ * Type of root that represents a storage service, such as a cloud-based
* service.
*
- * @see RootColumns#ROOT_TYPE
+ * @see #COLUMN_ROOT_TYPE
*/
public static final int ROOT_TYPE_SERVICE = 1;
/**
- * Root that represents a shortcut to content that may be available
- * elsewhere through another storage root.
+ * Type of root that represents a shortcut to content that may be
+ * available elsewhere through another storage root.
*
- * @see RootColumns#ROOT_TYPE
+ * @see #COLUMN_ROOT_TYPE
*/
public static final int ROOT_TYPE_SHORTCUT = 2;
/**
- * Root that represents a physical storage device.
+ * Type of root that represents a physical storage device.
*
- * @see RootColumns#ROOT_TYPE
+ * @see #COLUMN_ROOT_TYPE
*/
public static final int ROOT_TYPE_DEVICE = 3;
/**
- * Root that represents a physical storage device that should only be
- * displayed to advanced users.
+ * Flag indicating that at least one directory under this root supports
+ * creating content. Roots with this flag will be shown when an
+ * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
*
- * @see RootColumns#ROOT_TYPE
+ * @see #COLUMN_FLAGS
*/
- public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
- }
+ public static final int FLAG_SUPPORTS_CREATE = 1;
- /**
- * 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";
+ /**
+ * Flag indicating that this root offers content that is strictly local
+ * on the device. That is, no network requests are made for the content.
+ *
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_LOCAL_ONLY
+ */
+ public static final int FLAG_LOCAL_ONLY = 1 << 1;
/**
- * Storage root type, use for clustering. This field is read-only to
- * document clients.
- * <p>
- * Type: INTEGER (int)
+ * Flag indicating that this root should only be visible to advanced
+ * users.
*
- * @see Roots#ROOT_TYPE_SERVICE
- * @see Roots#ROOT_TYPE_DEVICE
+ * @see #COLUMN_FLAGS
*/
- public static final String ROOT_TYPE = "root_type";
+ public static final int FLAG_ADVANCED = 1 << 2;
/**
- * 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)
+ * Flag indicating that a root offers audio documents. When a user is
+ * selecting audio, roots not providing audio may be excluded.
+ *
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_MIME_TYPES
*/
- public static final String ICON = "icon";
+ public static final int FLAG_PROVIDES_AUDIO = 1 << 3;
/**
- * Title for this storage root, or {@code null} to use the default
- * {@link ProviderInfo#labelRes}. This field is read-only to document
- * clients.
- * <p>
- * Type: STRING
+ * Flag indicating that a root offers video documents. When a user is
+ * selecting video, roots not providing video may be excluded.
+ *
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_MIME_TYPES
*/
- public static final String TITLE = "title";
+ public static final int FLAG_PROVIDES_VIDEO = 1 << 4;
/**
- * Summary for this storage root, or {@code null} to omit. This field is
- * read-only to document clients.
- * <p>
- * Type: STRING
+ * Flag indicating that a root offers image documents. When a user is
+ * selecting images, roots not providing images may be excluded.
+ *
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_MIME_TYPES
*/
- public static final String SUMMARY = "summary";
+ public static final int FLAG_PROVIDES_IMAGES = 1 << 5;
/**
- * 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)
+ * Flag indicating that this root can report recently modified
+ * documents.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#buildRecentDocumentsUri(String, String)
*/
- public static final String AVAILABLE_BYTES = "available_bytes";
+ public static final int FLAG_SUPPORTS_RECENTS = 1 << 6;
+ }
+
+ /**
+ * Optional boolean flag included in a directory {@link Cursor#getExtras()}
+ * indicating that a document provider is still loading data. For example, a
+ * provider has returned some results, but is still waiting on an
+ * outstanding network request. The provider must send a content changed
+ * notification when loading is finished.
+ *
+ * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
+ * boolean)
+ */
+ public static final String EXTRA_LOADING = "loading";
+
+ /**
+ * Optional string included in a directory {@link Cursor#getExtras()}
+ * providing an informational message that should be shown to a user. For
+ * example, a provider may wish to indicate that not all documents are
+ * available.
+ */
+ public static final String EXTRA_INFO = "info";
+
+ /**
+ * Optional string included in a directory {@link Cursor#getExtras()}
+ * providing an error message that should be shown to a user. For example, a
+ * provider may wish to indicate that a network error occurred. The user may
+ * choose to retry, resulting in a new query.
+ */
+ public static final String EXTRA_ERROR = "error";
+
+ /** {@hide} */
+ public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
+ /** {@hide} */
+ public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
+
+ /** {@hide} */
+ public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
+
+ private static final String PATH_ROOT = "root";
+ private static final String PATH_RECENT = "recent";
+ private static final String PATH_DOCUMENT = "document";
+ private static final String PATH_CHILDREN = "children";
+ private static final String PATH_SEARCH = "search";
+
+ private static final String PARAM_QUERY = "query";
+
+ /**
+ * Build Uri representing the roots of a document provider. When queried, a
+ * provider will return one or more rows with columns defined by
+ * {@link Root}.
+ *
+ * @see DocumentsProvider#queryRoots(String[])
+ */
+ public static Uri buildRootsUri(String authority) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_ROOT).build();
+ }
+
+ /**
+ * Build Uri representing the recently modified documents of a specific
+ * root. When queried, a provider will return zero or more rows with columns
+ * defined by {@link Document}.
+ *
+ * @see DocumentsProvider#queryRecentDocuments(String, String[])
+ * @see #getRootId(Uri)
+ */
+ public static Uri buildRecentDocumentsUri(String authority, String rootId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
+ .appendPath(PATH_RECENT).build();
+ }
+
+ /**
+ * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+ * document provider. When queried, a provider will return a single row with
+ * columns defined by {@link Document}.
+ *
+ * @see DocumentsProvider#queryDocument(String, String[])
+ * @see #getDocumentId(Uri)
+ */
+ public static Uri buildDocumentUri(String authority, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
+ }
+
+ /**
+ * Build Uri representing the children of the given directory in a document
+ * provider. When queried, a provider will return zero or more rows with
+ * columns defined by {@link Document}.
+ *
+ * @param parentDocumentId the document to return children for, which must
+ * be a directory with MIME type of
+ * {@link Document#MIME_TYPE_DIR}.
+ * @see DocumentsProvider#queryChildDocuments(String, String[], String)
+ * @see #getDocumentId(Uri)
+ */
+ public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
+ .build();
+ }
+
+ /**
+ * Build Uri representing a search for matching documents under a specific
+ * directory in a document provider. When queried, a provider will return
+ * zero or more rows with columns defined by {@link Document}.
+ *
+ * @param parentDocumentId the document to return children for, which must
+ * be both a directory with MIME type of
+ * {@link Document#MIME_TYPE_DIR} and have
+ * {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set.
+ * @see DocumentsProvider#querySearchDocuments(String, String, String[])
+ * @see #getDocumentId(Uri)
+ * @see #getSearchDocumentsQuery(Uri)
+ */
+ public static Uri buildSearchDocumentsUri(
+ String authority, String parentDocumentId, String query) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH)
+ .appendQueryParameter(PARAM_QUERY, query).build();
+ }
+
+ /**
+ * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
+ */
+ public static String getRootId(Uri rootUri) {
+ final List<String> paths = rootUri.getPathSegments();
+ if (paths.size() < 2) {
+ throw new IllegalArgumentException("Not a root: " + rootUri);
+ }
+ if (!PATH_ROOT.equals(paths.get(0))) {
+ throw new IllegalArgumentException("Not a root: " + rootUri);
+ }
+ return paths.get(1);
+ }
+
+ /**
+ * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
+ */
+ public static String getDocumentId(Uri documentUri) {
+ final List<String> paths = documentUri.getPathSegments();
+ if (paths.size() < 2) {
+ throw new IllegalArgumentException("Not a document: " + documentUri);
+ }
+ if (!PATH_DOCUMENT.equals(paths.get(0))) {
+ throw new IllegalArgumentException("Not a document: " + documentUri);
+ }
+ return paths.get(1);
+ }
+
+ /**
+ * Extract the search query from a Uri built by
+ * {@link #buildSearchDocumentsUri(String, String, String)}.
+ */
+ public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
+ return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
}
/**
@@ -503,6 +586,7 @@ public final class DocumentsContract {
* {@link Intent#ACTION_CREATE_DOCUMENT}.
*
* @see Context#grantUriPermission(String, Uri, int)
+ * @see Context#revokeUriPermission(Uri, int)
* @see ContentResolver#getIncomingUriPermissionGrants(int, int)
*/
public static Uri[] getOpenDocuments(Context context) {
@@ -526,19 +610,28 @@ public final class DocumentsContract {
}
/**
- * Return thumbnail representing the document at the given URI. Callers are
- * responsible for their own in-memory caching. Given document must have
- * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
+ * Return thumbnail representing the document at the given Uri. Callers are
+ * responsible for their own in-memory caching.
*
+ * @param documentUri document to return thumbnail for, which must have
+ * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
+ * @param size optimal thumbnail size desired. A provider may return a
+ * thumbnail of a different size, but never more than double the
+ * requested size.
+ * @param signal signal used to indicate that caller is no longer interested
+ * in the thumbnail.
* @return decoded thumbnail, or {@code null} if problem was encountered.
+ * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+ * android.os.CancellationSignal)
*/
- public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
+ public static Bitmap getDocumentThumbnail(
+ ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
final Bundle openOpts = new Bundle();
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
AssetFileDescriptor afd = null;
try {
- afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
+ afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
final FileDescriptor fd = afd.getFileDescriptor();
final long offset = afd.getStartOffset();
@@ -589,43 +682,46 @@ public final class DocumentsContract {
}
/**
- * Create a new document under a specific parent document with the given
- * display name and MIME type.
+ * Create a new document with given MIME type and display name.
*
- * @param parentDocumentUri document with
- * {@link Documents#FLAG_SUPPORTS_CREATE}
- * @param displayName name for new document
- * @param mimeType MIME type for new document, which cannot be changed
- * @return newly created document Uri, or {@code null} if failed
+ * @param parentDocumentUri directory with
+ * {@link Document#FLAG_DIR_SUPPORTS_CREATE}
+ * @param mimeType MIME type of new document
+ * @param displayName name of new document
+ * @return newly created document, or {@code null} if failed
*/
- public static Uri createDocument(
- ContentResolver resolver, Uri parentDocumentUri, String displayName, String mimeType) {
- final ContentValues values = new ContentValues();
- values.put(DocumentColumns.MIME_TYPE, mimeType);
- values.put(DocumentColumns.DISPLAY_NAME, displayName);
- return resolver.insert(parentDocumentUri, values);
+ public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
+ String mimeType, String displayName) {
+ final Bundle in = new Bundle();
+ in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+ in.putString(Document.COLUMN_MIME_TYPE, mimeType);
+ in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
+
+ try {
+ final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in);
+ return buildDocumentUri(
+ parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to create document", e);
+ return null;
+ }
}
/**
- * Rename the document at the given URI. Given document must have
- * {@link Documents#FLAG_SUPPORTS_RENAME} set.
+ * Delete the given document.
*
- * @return if rename was successful.
+ * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
*/
- public static boolean renameDocument(
- ContentResolver resolver, Uri documentUri, String displayName) {
- final ContentValues values = new ContentValues();
- values.put(DocumentColumns.DISPLAY_NAME, displayName);
- return (resolver.update(documentUri, values, null, null) == 1);
- }
+ public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
+ final Bundle in = new Bundle();
+ in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
- /**
- * Notify the system that roots have changed for the given storage provider.
- * This signal is used to invalidate internal caches.
- */
- public static void notifyRootsChanged(Context context, String authority) {
- final Intent intent = new Intent(ACTION_DOCUMENT_CHANGED);
- intent.setData(buildRootsUri(authority));
- context.sendBroadcast(intent);
+ try {
+ final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to delete document", e);
+ return false;
+ }
}
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
new file mode 100644
index 0000000..09f4866
--- /dev/null
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -0,0 +1,419 @@
+/*
+ * 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.provider;
+
+import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
+import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
+import static android.provider.DocumentsContract.getDocumentId;
+import static android.provider.DocumentsContract.getRootId;
+import static android.provider.DocumentsContract.getSearchDocumentsQuery;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.OnCloseListener;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Base class for a document provider. A document provider should extend this
+ * class and implement the abstract methods.
+ * <p>
+ * Each document 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
+ * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
+ * <p>
+ * Documents can be either an openable file (with a specific MIME type), or a
+ * directory containing additional documents (with the
+ * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
+ * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
+ * {@link Document#COLUMN_DOCUMENT_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 abstract class DocumentsProvider extends ContentProvider {
+ private static final String TAG = "DocumentsProvider";
+
+ private static final int MATCH_ROOT = 1;
+ private static final int MATCH_RECENT = 2;
+ private static final int MATCH_DOCUMENT = 3;
+ private static final int MATCH_CHILDREN = 4;
+ private static final int MATCH_SEARCH = 5;
+
+ private String mAuthority;
+
+ private UriMatcher mMatcher;
+
+ /**
+ * Implementation is provided by the parent class.
+ */
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ mAuthority = info.authority;
+
+ mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mMatcher.addURI(mAuthority, "root", MATCH_ROOT);
+ mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
+ mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
+ mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+ mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH);
+
+ // Sanity check our setup
+ if (!info.exported) {
+ throw new SecurityException("Provider must be exported");
+ }
+ if (!info.grantUriPermissions) {
+ throw new SecurityException("Provider must grantUriPermissions");
+ }
+ if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
+ || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
+ throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
+ }
+
+ super.attachInfo(context, info);
+ }
+
+ /**
+ * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
+ * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to
+ * represent the document, which must not change once returned.
+ *
+ * @param documentId the parent directory to create the new document under.
+ * @param mimeType the MIME type associated with the new document.
+ * @param displayName the display name of the new document.
+ */
+ @SuppressWarnings("unused")
+ public String createDocument(String documentId, String mimeType, String displayName)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("Create not supported");
+ }
+
+ /**
+ * Delete the given document. Upon returning, any Uri permission grants for
+ * the given document will be revoked. If additional documents were deleted
+ * as a side effect of this call, such as documents inside a directory, the
+ * implementor is responsible for revoking those permissions.
+ *
+ * @param documentId the document to delete.
+ */
+ @SuppressWarnings("unused")
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ throw new UnsupportedOperationException("Delete not supported");
+ }
+
+ public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
+
+ @SuppressWarnings("unused")
+ public Cursor queryRecentDocuments(String rootId, String[] projection)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("Recent not supported");
+ }
+
+ /**
+ * Return metadata for the given document. A provider should avoid making
+ * network requests to keep this request fast.
+ *
+ * @param documentId the document to return.
+ */
+ public abstract Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException;
+
+ /**
+ * Return the children of the given document which is a directory.
+ *
+ * @param parentDocumentId the directory to return children for.
+ */
+ public abstract Cursor queryChildDocuments(
+ String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException;
+
+ /**
+ * Return documents that that match the given query, starting the search at
+ * the given directory.
+ *
+ * @param parentDocumentId the directory to start search at.
+ */
+ @SuppressWarnings("unused")
+ public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("Search not supported");
+ }
+
+ /**
+ * Return MIME type for the given document. Must match the value of
+ * {@link Document#COLUMN_MIME_TYPE} for this document.
+ */
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ final Cursor cursor = queryDocument(documentId, null);
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
+ } else {
+ return null;
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+ }
+
+ /**
+ * Open and return the requested document. A provider should return a
+ * reliable {@link ParcelFileDescriptor} to detect when the remote caller
+ * has finished reading or writing the document. A provider may return a
+ * pipe or socket pair if the mode is exclusively
+ * {@link ParcelFileDescriptor#MODE_READ_ONLY} or
+ * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, but complex modes like
+ * {@link ParcelFileDescriptor#MODE_READ_WRITE} require a normal file on
+ * disk. If a provider blocks while downloading content, it should
+ * periodically check {@link CancellationSignal#isCanceled()} to abort
+ * abandoned open requests.
+ *
+ * @param docId the document to return.
+ * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
+ * @param signal used by the caller to signal if the request should be
+ * cancelled.
+ * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
+ * OnCloseListener)
+ * @see ParcelFileDescriptor#createReliablePipe()
+ * @see ParcelFileDescriptor#createReliableSocketPair()
+ */
+ public abstract ParcelFileDescriptor openDocument(
+ String docId, String mode, CancellationSignal signal) throws FileNotFoundException;
+
+ /**
+ * Open and return a thumbnail of the requested document. A provider should
+ * return a thumbnail closely matching the hinted size, attempting to serve
+ * from a local cache if possible. A provider should never return images
+ * more than double the hinted size. If a provider performs expensive
+ * operations to download or generate a thumbnail, it should periodically
+ * check {@link CancellationSignal#isCanceled()} to abort abandoned
+ * thumbnail requests.
+ *
+ * @param docId the document to return.
+ * @param sizeHint hint of the optimal thumbnail dimensions.
+ * @param signal used by the caller to signal if the request should be
+ * cancelled.
+ * @see Document#FLAG_SUPPORTS_THUMBNAIL
+ */
+ @SuppressWarnings("unused")
+ public AssetFileDescriptor openDocumentThumbnail(
+ String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+ throw new UnsupportedOperationException("Thumbnails not supported");
+ }
+
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
+ * @see #queryRoots(String[])
+ * @see #queryRecentDocuments(String, String[])
+ * @see #queryDocument(String, String[])
+ * @see #queryChildDocuments(String, String[], String)
+ * @see #querySearchDocuments(String, String, String[])
+ */
+ @Override
+ public final Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ switch (mMatcher.match(uri)) {
+ case MATCH_ROOT:
+ return queryRoots(projection);
+ case MATCH_RECENT:
+ return queryRecentDocuments(getRootId(uri), projection);
+ case MATCH_DOCUMENT:
+ return queryDocument(getDocumentId(uri), projection);
+ case MATCH_CHILDREN:
+ return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
+ case MATCH_SEARCH:
+ return querySearchDocuments(
+ getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
+ default:
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Failed during query", e);
+ return null;
+ }
+ }
+
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
+ * @see #getDocumentType(String)
+ */
+ @Override
+ public final String getType(Uri uri) {
+ try {
+ switch (mMatcher.match(uri)) {
+ case MATCH_DOCUMENT:
+ return getDocumentType(getDocumentId(uri));
+ default:
+ return null;
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Failed during getType", e);
+ return null;
+ }
+ }
+
+ /**
+ * Implementation is provided by the parent class. Throws by default, and
+ * cannot be overriden.
+ *
+ * @see #createDocument(String, String, String)
+ */
+ @Override
+ public final Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert not supported");
+ }
+
+ /**
+ * Implementation is provided by the parent class. Throws by default, and
+ * cannot be overriden.
+ *
+ * @see #deleteDocument(String)
+ */
+ @Override
+ public final int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Delete not supported");
+ }
+
+ /**
+ * Implementation is provided by the parent class. Throws by default, and
+ * cannot be overriden.
+ */
+ @Override
+ public final int update(
+ Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update not supported");
+ }
+
+ /** {@hide} */
+ @Override
+ public final Bundle callFromPackage(
+ String callingPackage, String method, String arg, Bundle extras) {
+ if (!method.startsWith("android:")) {
+ // Let non-platform methods pass through
+ return super.callFromPackage(callingPackage, method, arg, extras);
+ }
+
+ // Require that caller can manage given document
+ final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
+ final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+ getContext().enforceCallingOrSelfUriPermission(
+ documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
+
+ final Bundle out = new Bundle();
+ try {
+ if (METHOD_CREATE_DOCUMENT.equals(method)) {
+ final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+
+ // TODO: issue Uri grant towards calling package
+ // TODO: enforce that package belongs to caller
+ final String newDocumentId = createDocument(documentId, mimeType, displayName);
+ out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
+
+ } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+ final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID);
+ deleteDocument(docId);
+
+ } else {
+ throw new UnsupportedOperationException("Method not supported " + method);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException("Failed call " + method, e);
+ }
+ return out;
+ }
+
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ */
+ @Override
+ public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ return openDocument(getDocumentId(uri), mode, null);
+ }
+
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ */
+ @Override
+ public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ return openDocument(getDocumentId(uri), mode, signal);
+ }
+
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+ */
+ @Override
+ public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+ throws FileNotFoundException {
+ if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
+ final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
+ return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
+ } else {
+ return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
+ }
+ }
+
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+ */
+ @Override
+ public final AssetFileDescriptor openTypedAssetFile(
+ Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+ throws FileNotFoundException {
+ if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
+ final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
+ return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
+ } else {
+ return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
+ }
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0b51b8a..83e1544 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -690,6 +690,19 @@ public final class Settings {
public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS
= "android.settings.NOTIFICATION_LISTENER_SETTINGS";
+ /**
+ * Activity Action: Show settings for video captioning.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -2895,6 +2908,11 @@ public final class Settings {
/** @hide */
public static int getIntForUser(ContentResolver cr, String name, int def, 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 getLocationModeForUser(cr, userHandle);
+ }
String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Integer.parseInt(v) : def;
@@ -2929,6 +2947,11 @@ public final class Settings {
/** @hide */
public static int getIntForUser(ContentResolver cr, String name, int userHandle)
throws SettingNotFoundException {
+ 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);
+ }
String v = getStringForUser(cr, name, userHandle);
try {
return Integer.parseInt(v);
@@ -2957,6 +2980,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,11 +3293,25 @@ 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";
/**
- * Location access disabled
+ * The degree of location access enabled by the user.
+ * <p/>
+ * When used with {@link #putInt(ContentResolver, String, int)}, must be one of {@link
+ * #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, {@link
+ * #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. When used with {@link
+ * #getInt(ContentResolver, String)}, the caller must gracefully handle additional location
+ * modes that might be added in the future.
+ */
+ public static final String LOCATION_MODE = "location_mode";
+
+ /**
+ * Location access disabled.
*/
public static final int LOCATION_MODE_OFF = 0;
/**
@@ -3559,7 +3601,7 @@ public final class Settings {
* <li>{@link #ACCESSIBILITY_CAPTIONING_EDGE_COLOR}
* <li>{@link #ACCESSIBILITY_CAPTIONING_EDGE_TYPE}
* <li>{@link #ACCESSIBILITY_CAPTIONING_TYPEFACE}
- * <li>{@link #ACCESSIBILITY_CAPTIONING_FONT_SIZE}
+ * <li>{@link #ACCESSIBILITY_CAPTIONING_FONT_SCALE}
* </ul>
*
* @hide
@@ -3581,9 +3623,8 @@ public final class Settings {
* Integer property that specifies the preset style for captions, one
* of:
* <ul>
- * <li>{@link android.view.accessibility.CaptioningManager#PRESET_WHITE_ON_BLACK}
- * <li>{@link android.view.accessibility.CaptioningManager#PRESET_BLACK_ON_WHITE}
- * <li>{@link android.view.accessibility.CaptioningManager#PRESET_CUSTOM}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#PRESET_CUSTOM}
+ * <li>a valid index of {@link android.view.accessibility.CaptioningManager.CaptionStyle#PRESETS}
* </ul>
*
* @see java.util.Locale#toString
@@ -3615,9 +3656,9 @@ public final class Settings {
/**
* Integer property that specifes the edge type for captions, one of:
* <ul>
- * <li>{@link android.view.accessibility.CaptioningManager#EDGE_TYPE_NONE}
- * <li>{@link android.view.accessibility.CaptioningManager#EDGE_TYPE_OUTLINE}
- * <li>{@link android.view.accessibility.CaptioningManager#EDGE_TYPE_DROP_SHADOWED}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_NONE}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_OUTLINE}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_DROP_SHADOW}
* </ul>
*
* @see #ACCESSIBILITY_CAPTIONING_EDGE_COLOR
@@ -3653,13 +3694,12 @@ public final class Settings {
"accessibility_captioning_typeface";
/**
- * Integer point property that specifies font size for captions in
- * scaled pixels (sp).
+ * Floating point property that specifies font scaling for captions.
*
* @hide
*/
- public static final String ACCESSIBILITY_CAPTIONING_FONT_SIZE =
- "accessibility_captioning_font_size";
+ public static final String ACCESSIBILITY_CAPTIONING_FONT_SCALE =
+ "accessibility_captioning_font_scale";
/**
* The timout for considering a press to be a long press in milliseconds.
@@ -3668,13 +3708,22 @@ public final class Settings {
public static final String LONG_PRESS_TIMEOUT = "long_press_timeout";
/**
- * List of the enabled print providers.
+ * List of the enabled print services.
* @hide
*/
public static final String ENABLED_PRINT_SERVICES =
"enabled_print_services";
/**
+ * List of the system print services we enabled on first boot. On
+ * first boot we enable all system, i.e. bundled print services,
+ * once, so they work out-of-the-box.
+ * @hide
+ */
+ public static final String ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES =
+ "enabled_on_first_boot_system_print_services";
+
+ /**
* Setting to always use the default text-to-speech settings regardless
* of the application settings.
* 1 = override application settings,
@@ -4256,12 +4305,6 @@ public final class Settings {
public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
/**
- * Whether to automatically invoke NFC payment app or manually select on tap.
- * @hide
- */
- public static final String NFC_PAYMENT_MODE = "nfc_payment_mode";
-
- /**
* Name of a package that the current user has explicitly allowed to see all of that
* user's notifications.
*
@@ -4304,7 +4347,7 @@ public final class Settings {
ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
ACCESSIBILITY_CAPTIONING_TYPEFACE,
- ACCESSIBILITY_CAPTIONING_FONT_SIZE,
+ ACCESSIBILITY_CAPTIONING_FONT_SCALE,
TTS_USE_DEFAULTS,
TTS_DEFAULT_RATE,
TTS_DEFAULT_PITCH,
@@ -4328,7 +4371,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 +4384,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 +4400,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 +4410,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 +4432,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);
}
}
@@ -4398,10 +4445,12 @@ public final class Settings {
* @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
*/
- public static final void setLocationModeForUser(ContentResolver cr, int mode, int userId) {
+ private static final boolean setLocationModeForUser(ContentResolver cr, int mode,
+ int userId) {
synchronized (mLocationSettingsLock) {
boolean gps = false;
boolean network = false;
@@ -4421,28 +4470,15 @@ 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;
}
}
/**
- * Thread-safe method for setting the location mode to one of
- * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
- * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
- *
- * @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
- */
- public static final void setLocationMode(ContentResolver cr, int mode) {
- setLocationModeForUser(cr, mode, UserHandle.myUserId());
- }
-
- /**
* Thread-safe method for reading the location mode, returns one of
* {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
* {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
@@ -4451,7 +4487,7 @@ public final class Settings {
* @param userId the userId for which to read the mode
* @return the location mode
*/
- public static final int getLocationModeForUser(ContentResolver cr, int userId) {
+ private static final int getLocationModeForUser(ContentResolver cr, int userId) {
synchronized (mLocationSettingsLock) {
boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser(
cr, LocationManager.GPS_PROVIDER, userId);
@@ -4468,18 +4504,6 @@ public final class Settings {
}
}
}
-
- /**
- * Thread-safe method for reading the location mode, returns one of
- * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
- * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
- *
- * @param cr the content resolver to use
- * @return the location mode
- */
- public static final int getLocationMode(ContentResolver cr) {
- return getLocationModeForUser(cr, UserHandle.myUserId());
- }
}
/**
diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java
index 3d75dc8..bf8d4e5 100644
--- a/core/java/android/security/IKeystoreService.java
+++ b/core/java/android/security/IKeystoreService.java
@@ -244,7 +244,8 @@ public interface IKeystoreService extends IInterface {
return _result;
}
- public int generate(String name, int uid, int flags) throws RemoteException {
+ public int generate(String name, int uid, int keyType, int keySize, int flags,
+ byte[][] args) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
@@ -252,7 +253,17 @@ public interface IKeystoreService extends IInterface {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
_data.writeInt(uid);
+ _data.writeInt(keyType);
+ _data.writeInt(keySize);
_data.writeInt(flags);
+ if (args == null) {
+ _data.writeInt(0);
+ } else {
+ _data.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ _data.writeByteArray(args[i]);
+ }
+ }
mRemote.transact(Stub.TRANSACTION_generate, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
@@ -560,7 +571,8 @@ public interface IKeystoreService extends IInterface {
public int zero() throws RemoteException;
- public int generate(String name, int uid, int flags) throws RemoteException;
+ public int generate(String name, int uid, int keyType, int keySize, int flags, byte[][] args)
+ throws RemoteException;
public int import_key(String name, byte[] data, int uid, int flags) throws RemoteException;
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..521d06d
--- /dev/null
+++ b/core/java/android/speech/hotword/HotwordRecognitionService.java
@@ -0,0 +1,235 @@
+/*
+ * 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.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
+ mCurrentCallback.onHotwordRecognitionStopped();
+ mCurrentCallback = null;
+ HotwordRecognitionService.this.onStopHotwordRecognition();
+ }
+ } 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();
+ }
+
+ /**
+ * 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.
+ */
+ public abstract void onStopHotwordRecognition();
+
+ /** 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.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..82cec10
--- /dev/null
+++ b/core/java/android/speech/hotword/HotwordRecognizer.java
@@ -0,0 +1,393 @@
+/*
+ * 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.ApplicationInfo;
+import android.content.pm.PackageManager;
+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;
+
+ /** 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;
+ }
+ }
+ };
+
+ /**
+ * 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}.
+ *
+ * @param context in which to create {@code HotwordRecognizer}
+ * @return a new {@code HotwordRecognizer}
+ */
+ public static HotwordRecognizer createHotwordRecognizer(final Context context) {
+ ComponentName serviceComponent = null;
+ // Resolve to a default ComponentName.
+ final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
+ new Intent(HotwordRecognitionService.SERVICE_INTERFACE), 0);
+ for (int i = 0; i < list.size(); i++) {
+ final ResolveInfo ri = list.get(i);
+ if (!ri.serviceInfo.enabled) {
+ continue;
+ }
+ if ((ri.serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM)
+ != PackageManager.MATCH_DEFAULT_ONLY) {
+ serviceComponent = new ComponentName(
+ ri.serviceInfo.packageName, ri.serviceInfo.name);
+ break;
+ }
+ }
+ // If all else fails, pick the first one.
+ if (serviceComponent == null && !list.isEmpty()) {
+ serviceComponent = new ComponentName(
+ list.get(0).serviceInfo.packageName, list.get(0).serviceInfo.name);
+ }
+ return createHotwordRecognizer(context, serviceComponent);
+ }
+
+ /**
+ * Factory method to create a new {@code HotwordRecognizer}.
+ *
+ * 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);
+ }
+
+ /**
+ * Starts recognizing hotword and sets the listener that will receive the callbacks.
+ *
+ * @param listener listener that will receive all the callbacks from the created
+ * {@link HotwordRecognizer}, this must not be null.
+ */
+ public void startRecognition(HotwordRecognitionListener listener) {
+ checkIsCalledFromMainThread();
+ if (mConnection == null) { // first time connection
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ mConnection = new Connection();
+ Intent serviceIntent = new Intent(HotwordRecognitionService.SERVICE_INTERFACE);
+ mListener.mInternalListener = listener;
+
+ if (mServiceComponent == null) {
+ Log.e(TAG, "no selected voice recognition service");
+ mListener.onHotwordError(ERROR_CLIENT);
+ return;
+ } 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;
+ }
+ putMessage(Message.obtain(mHandler, MSG_START));
+ } else {
+ mListener.onHotwordError(ERROR_SERVICE_ALREADY_STARTED);
+ return;
+ }
+ }
+
+ /**
+ * Stops recognizing hotword.
+ */
+ public void stopRecognition() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_STOP));
+ }
+
+ // Private constructor.
+ private HotwordRecognizer(Context context, ComponentName serviceComponent) {
+ mContext = context;
+ mServiceComponent = serviceComponent;
+ }
+
+ 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 (mConnection != null) {
+ mContext.unbindService(mConnection);
+ }
+ if (DBG) Log.d(TAG, "service stopRecognition command succeeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "stopRecognition() failed", e);
+ mListener.onHotwordError(ERROR_CLIENT);
+ } finally {
+ mPendingTasks.clear();
+ mService = null;
+ mConnection = null;
+ mListener.mInternalListener = null;
+ }
+ }
+
+ 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/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
index 917a109..12a026b 100644
--- a/core/java/android/speech/tts/SynthesisRequest.java
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -30,7 +30,7 @@ import android.os.Bundle;
* </ul>
*
* Any additional parameters sent to the text to speech service are passed in
- * uninterpreted, see the @code{params} argument in {@link TextToSpeech#speak}
+ * uninterpreted, see the {@code params} argument in {@link TextToSpeech#speak}
* and {@link TextToSpeech#synthesizeToFile}.
*/
public final class SynthesisRequest {
diff --git a/core/java/android/util/LayoutDirection.java b/core/java/android/util/LayoutDirection.java
index e37d2f2..20af20b 100644
--- a/core/java/android/util/LayoutDirection.java
+++ b/core/java/android/util/LayoutDirection.java
@@ -17,11 +17,15 @@
package android.util;
/**
- * An interface for defining layout directions. A layout direction can be left-to-right (LTR)
+ * A class for defining layout directions. A layout direction can be left-to-right (LTR)
* or right-to-left (RTL). It can also be inherited (from a parent) or deduced from the default
* language script of a locale.
*/
-public interface LayoutDirection {
+public final class LayoutDirection {
+
+ // No instantiation
+ private LayoutDirection() {}
+
/**
* Horizontal layout direction is from Left to Right.
*/
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 51c5c7b..0bebc04 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -18,6 +18,8 @@ package android.view;
import android.content.Context;
import android.content.res.Resources;
+import android.os.Build;
+import android.os.Handler;
import android.os.SystemClock;
import android.util.FloatMath;
@@ -128,6 +130,8 @@ public class ScaleGestureDetector {
private float mFocusX;
private float mFocusY;
+ private boolean mDoubleTapScales;
+
private float mCurrSpan;
private float mPrevSpan;
private float mInitialSpan;
@@ -148,9 +152,14 @@ public class ScaleGestureDetector {
private int mTouchHistoryDirection;
private long mTouchHistoryLastAcceptedTime;
private int mTouchMinMajor;
+ private MotionEvent mDoubleTapEvent;
+ private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+ private final Handler mHandler;
private static final long TOUCH_STABILIZE_TIME = 128; // ms
- private static final int TOUCH_MIN_MAJOR = 48; // dp
+ private static final int DOUBLE_TAP_MODE_NONE = 0;
+ private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1;
+
/**
* Consistency verifier for debugging purposes.
@@ -158,8 +167,37 @@ public class ScaleGestureDetector {
private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
+ private GestureDetector mGestureDetector;
+
+ private boolean mEventBeforeOrAboveStartingGestureEvent;
+ /**
+ * Creates a ScaleGestureDetector with the supplied listener.
+ * You may only use this constructor from a {@link android.os.Looper Looper} thread.
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
+ this(context, listener, null);
+ }
+
+ /**
+ * Creates a ScaleGestureDetector with the supplied listener.
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use for running deferred listener events.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public ScaleGestureDetector(Context context, OnScaleGestureListener listener,
+ Handler handler) {
mContext = context;
mListener = listener;
mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
@@ -167,8 +205,12 @@ public class ScaleGestureDetector {
final Resources res = context.getResources();
mTouchMinMajor = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_minScalingTouchMajor);
- mMinSpan = res.getDimensionPixelSize(
- com.android.internal.R.dimen.config_minScalingSpan);
+ mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);
+ mHandler = handler;
+ // Quick scale is enabled by default after JB_MR2
+ if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ setQuickScaleEnabled(true);
+ }
}
/**
@@ -263,8 +305,14 @@ public class ScaleGestureDetector {
final int action = event.getActionMasked();
+ // Forward the event to check for double tap gesture
+ if (mDoubleTapScales) {
+ mGestureDetector.onTouchEvent(event);
+ }
+
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_CANCEL;
+
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// Reset any scale in progress with the listener.
// If it's an ACTION_DOWN we're beginning a new event stream.
@@ -273,6 +321,7 @@ public class ScaleGestureDetector {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
+ mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
}
if (streamComplete) {
@@ -284,21 +333,37 @@ public class ScaleGestureDetector {
final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_UP ||
action == MotionEvent.ACTION_POINTER_DOWN;
+
+
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? event.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = event.getPointerCount();
- for (int i = 0; i < count; i++) {
- if (skipIndex == i) continue;
- sumX += event.getX(i);
- sumY += event.getY(i);
- }
final int div = pointerUp ? count - 1 : count;
- final float focusX = sumX / div;
- final float focusY = sumY / div;
+ final float focusX;
+ final float focusY;
+ if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) {
+ // In double tap mode, the focal pt is always where the double tap
+ // gesture started
+ focusX = mDoubleTapEvent.getX();
+ focusY = mDoubleTapEvent.getY();
+ if (event.getY() < focusY) {
+ mEventBeforeOrAboveStartingGestureEvent = true;
+ } else {
+ mEventBeforeOrAboveStartingGestureEvent = false;
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
+ }
+ focusX = sumX / div;
+ focusY = sumY / div;
+ }
addTouchHistory(event);
@@ -320,7 +385,12 @@ public class ScaleGestureDetector {
// the focal point.
final float spanX = devX * 2;
final float spanY = devY * 2;
- final float span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
+ final float span;
+ if (inDoubleTapMode()) {
+ span = spanY;
+ } else {
+ span = FloatMath.sqrt(spanX * spanX + spanY * spanY);
+ }
// Dispatch begin/end events as needed.
// If the configuration changes, notify the app to reset its current state by beginning
@@ -328,10 +398,11 @@ public class ScaleGestureDetector {
final boolean wasInProgress = mInProgress;
mFocusX = focusX;
mFocusY = focusY;
- if (mInProgress && (span < mMinSpan || configChanged)) {
+ if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
+ mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
}
if (configChanged) {
mPrevSpanX = mCurrSpanX = spanX;
@@ -354,6 +425,7 @@ public class ScaleGestureDetector {
mCurrSpan = span;
boolean updatePrev = true;
+
if (mInProgress) {
updatePrev = mListener.onScale(this);
}
@@ -369,6 +441,34 @@ public class ScaleGestureDetector {
return true;
}
+
+ private boolean inDoubleTapMode() {
+ return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
+ }
+
+ /**
+ * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks
+ * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default
+ * if the app targets API 19 and newer.
+ * @param scales true to enable quick scaling, false to disable
+ */
+ public void setQuickScaleEnabled(boolean scales) {
+ mDoubleTapScales = scales;
+ if (mDoubleTapScales && mGestureDetector == null) {
+ GestureDetector.SimpleOnGestureListener gestureListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ // Double tap: start watching for a swipe
+ mDoubleTapEvent = e;
+ mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS;
+ return true;
+ }
+ };
+ mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
+ }
+ }
+
/**
* Returns {@code true} if a scale gesture is in progress.
*/
@@ -472,6 +572,12 @@ public class ScaleGestureDetector {
* @return The current scaling factor.
*/
public float getScaleFactor() {
+ if (inDoubleTapMode() && mEventBeforeOrAboveStartingGestureEvent) {
+ // Drag is moving up; the further away from the gesture
+ // start, the smaller the span should be, the closer,
+ // the larger the span, and therefore the larger the scale
+ return (1 / mCurrSpan) / (1 / mPrevSpan);
+ }
return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
}
@@ -493,4 +599,4 @@ public class ScaleGestureDetector {
public long getEventTime() {
return mCurrTime;
}
-}
+} \ No newline at end of file
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5269ee3..907290b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2375,20 +2375,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
/**
- * Flag for {@link #setSystemUiVisibility(int)}: View would like to receive touch events
- * when hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the
- * navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} instead of having the system
- * clear these flags upon interaction. The system may compensate by temporarily overlaying
- * semi-transparent system bars while also delivering the event.
- */
- public static final int SYSTEM_UI_FLAG_ALLOW_TRANSIENT = 0x00000800;
+ * Flag for {@link #setSystemUiVisibility(int)}: View would like to remain interactive when
+ * hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the navigation
+ * bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. Use this flag to create an immersive
+ * experience while also hiding the system bars. If this flag is not set,
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} will be force cleared by the system on any user
+ * interaction, and {@link #SYSTEM_UI_FLAG_FULLSCREEN} will be force-cleared by the system
+ * if the user swipes from the top of the screen.
+ * <p>When system bars are hidden in immersive mode, they can be revealed temporarily with
+ * system gestures, such as swiping from the top of the screen. These transient system bars
+ * will overlay app’s content, may have some degree of transparency, and will automatically
+ * hide after a short timeout.
+ * </p><p>Since this flag is a modifier for {@link #SYSTEM_UI_FLAG_FULLSCREEN} and
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, it only has an effect when used in combination
+ * with one or both of those flags.</p>
+ */
+ public static final int SYSTEM_UI_FLAG_IMMERSIVE = 0x00000800;
/**
* Flag for {@link #setSystemUiVisibility(int)}: View would like the status bar to have
* transparency.
*
* <p>The transparency request may be denied if the bar is in another mode with a specific
- * style, like {@link #SYSTEM_UI_FLAG_ALLOW_TRANSIENT transient mode}.
+ * style, like {@link #SYSTEM_UI_FLAG_IMMERSIVE immersive mode}.
*/
public static final int SYSTEM_UI_FLAG_TRANSPARENT_STATUS = 0x00001000;
@@ -2397,7 +2406,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* transparency.
*
* <p>The transparency request may be denied if the bar is in another mode with a specific
- * style, like {@link #SYSTEM_UI_FLAG_ALLOW_TRANSIENT transient mode}.
+ * style, like {@link #SYSTEM_UI_FLAG_IMMERSIVE immersive mode}.
*/
public static final int SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION = 0x00002000;
@@ -3390,17 +3399,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
- * @param defStyle The default style to apply to this view. If 0, no style
- * will be applied (beyond what is included in the theme). This may
- * either be an attribute resource, whose value will be retrieved
- * from the current theme, or an explicit style resource.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource to apply to this view. If 0, no
+ * default style will be applied.
* @see #View(Context, AttributeSet)
*/
- public View(Context context, AttributeSet attrs, int defStyle) {
+ public View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context);
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
- defStyle, 0);
+ defStyleAttr, 0);
Drawable background = null;
@@ -6680,8 +6688,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 +7398,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void dispatchStartTemporaryDetach() {
- clearAccessibilityFocus();
clearDisplayList();
onStartTemporaryDetach();
@@ -8419,6 +8427,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Implement this method to handle touch screen motion events.
+ * <p>
+ * If this method is used to detect click actions, it is recommended that
+ * the actions be performed by implementing and calling
+ * {@link #performClick()}. This will ensure consistent system behavior,
+ * including:
+ * <ul>
+ * <li>obeying click sound preferences
+ * <li>dispatching OnClickListener calls
+ * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
+ * accessibility features are enabled
+ * </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
@@ -11860,7 +11879,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
jumpDrawablesToCurrentState();
- clearAccessibilityFocus();
resetSubtreeAccessibilityStateChanged();
if (isFocused()) {
@@ -13238,14 +13256,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
case DRAWING_CACHE_QUALITY_AUTO:
- quality = Bitmap.Config.ARGB_8888;
- break;
case DRAWING_CACHE_QUALITY_LOW:
- quality = Bitmap.Config.ARGB_8888;
- break;
case DRAWING_CACHE_QUALITY_HIGH:
- quality = Bitmap.Config.ARGB_8888;
- break;
default:
quality = Bitmap.Config.ARGB_8888;
break;
@@ -16654,7 +16666,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
* {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
- * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_ALLOW_TRANSIENT},
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
* {@link #SYSTEM_UI_FLAG_TRANSPARENT_STATUS},
* and {@link #SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION}.
*/
@@ -16672,7 +16684,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
* {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
- * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_ALLOW_TRANSIENT},
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_IMMERSIVE},
* {@link #SYSTEM_UI_FLAG_TRANSPARENT_STATUS},
* and {@link #SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION}.
*/
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d68c410..03a9c37 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3865,8 +3865,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public void setLayoutTransition(LayoutTransition transition) {
if (mTransition != null) {
- mTransition.cancel();
- mTransition.removeTransitionListener(mLayoutTransitionListener);
+ LayoutTransition previousTransition = mTransition;
+ previousTransition.cancel();
+ previousTransition.removeTransitionListener(mLayoutTransitionListener);
}
mTransition = transition;
if (mTransition != null) {
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/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 6d54094..ba63421 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -518,7 +518,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mTextSelectionEnd = UNDEFINED;
private int mInputType = InputType.TYPE_NULL;
- private Bundle mBundle;
+ private Bundle mExtras;
private int mConnectionId = UNDEFINED;
@@ -1471,9 +1471,18 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
- * Gets if the node is a live region for whose changes the user
- * should be notified. It is the responsibility of the accessibility
+ * Gets if the node is a live region.
+ * <p>
+ * A live region is a node that contains information that is important
+ * for the user and when it changes the user has to be notified. For
+ * example, if the user plays a video and the application shows a
+ * progress indicator with the percentage of buffering, then the progress
+ * indicator should be marked as a live region.
+ * </p>
+ * <p>
+ * It is the responsibility of the accessibility
* service to monitor this region and notify the user if it changes.
+ * </p>
*
* @return If the node is a live region.
*/
@@ -1525,7 +1534,7 @@ public class AccessibilityNodeInfo implements Parcelable {
*
* @return If the the node opens a popup.
*/
- public boolean getOpensPopup() {
+ public boolean canOpenPopup() {
return getBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP);
}
@@ -1539,7 +1548,8 @@ public class AccessibilityNodeInfo implements Parcelable {
*
* @param opensPopup If the the node opens a popup.
*/
- public void setOpensPopup(boolean opensPopup) {
+ public void setCanOpenPopup(boolean opensPopup) {
+ enforceNotSealed();
setBooleanProperty(BOOLEAN_PROPERTY_OPENS_POPUP, opensPopup);
}
@@ -1927,7 +1937,7 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
- * Gets an optional bundle with additional data. The bundle
+ * Gets an optional bundle with extra data. The bundle
* is lazily created and never <code>null</code>.
* <p>
* <strong>Note:</strong> It is recommended to use the package
@@ -1939,11 +1949,11 @@ public class AccessibilityNodeInfo implements Parcelable {
*
* @return The bundle.
*/
- public Bundle getBundle() {
- if (mBundle == null) {
- mBundle = new Bundle();
+ public Bundle getExtras() {
+ if (mExtras == null) {
+ mExtras = new Bundle();
}
- return mBundle;
+ return mExtras;
}
/**
@@ -2194,9 +2204,9 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeInt(mTextSelectionEnd);
parcel.writeInt(mInputType);
- if (mBundle != null) {
+ if (mExtras != null) {
parcel.writeInt(1);
- parcel.writeBundle(mBundle);
+ parcel.writeBundle(mExtras);
} else {
parcel.writeInt(0);
}
@@ -2213,8 +2223,8 @@ public class AccessibilityNodeInfo implements Parcelable {
if (mCollectionInfo != null) {
parcel.writeInt(1);
- parcel.writeInt(mCollectionInfo.getHorizontalSize());
- parcel.writeInt(mCollectionInfo.getVerticalSize());
+ parcel.writeInt(mCollectionInfo.getRowCount());
+ parcel.writeInt(mCollectionInfo.getColumnCount());
parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
} else {
parcel.writeInt(0);
@@ -2222,10 +2232,10 @@ public class AccessibilityNodeInfo implements Parcelable {
if (mCollectionItemInfo != null) {
parcel.writeInt(1);
- parcel.writeInt(mCollectionItemInfo.getHorizontalPosition());
- parcel.writeInt(mCollectionItemInfo.getHorizontalSpan());
- parcel.writeInt(mCollectionItemInfo.getVerticalPosition());
- parcel.writeInt(mCollectionItemInfo.getVerticalSpan());
+ parcel.writeInt(mCollectionItemInfo.getColumnIndex());
+ parcel.writeInt(mCollectionItemInfo.getColumnSpan());
+ parcel.writeInt(mCollectionItemInfo.getRowIndex());
+ parcel.writeInt(mCollectionItemInfo.getRowSpan());
parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
} else {
parcel.writeInt(0);
@@ -2266,8 +2276,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mTextSelectionStart = other.mTextSelectionStart;
mTextSelectionEnd = other.mTextSelectionEnd;
mInputType = other.mInputType;
- if (other.mBundle != null && !other.mBundle.isEmpty()) {
- getBundle().putAll(other.mBundle);
+ if (other.mExtras != null && !other.mExtras.isEmpty()) {
+ getExtras().putAll(other.mExtras);
}
mRangeInfo = other.mRangeInfo;
mCollectionInfo = other.mCollectionInfo;
@@ -2323,7 +2333,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mInputType = parcel.readInt();
if (parcel.readInt() == 1) {
- getBundle().putAll(parcel.readBundle());
+ getExtras().putAll(parcel.readBundle());
}
if (parcel.readInt() == 1) {
@@ -2376,8 +2386,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mTextSelectionStart = UNDEFINED;
mTextSelectionEnd = UNDEFINED;
mInputType = InputType.TYPE_NULL;
- if (mBundle != null) {
- mBundle.clear();
+ if (mExtras != null) {
+ mExtras.clear();
}
if (mRangeInfo != null) {
mRangeInfo.recycle();
@@ -2677,7 +2687,15 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Class with information if a node is a collection. Use
- * {@link CollectionInfo#obtain(int, int, boolean) to an instance.
+ * {@link CollectionInfo#obtain(int, int, boolean)} to get an instance.
+ * <p>
+ * A collection of items has rows and columns and may be hierarchical.
+ * For example, a horizontal list is a collection with one column, as
+ * many rows as the list items, and is not hierarchical; A table is a
+ * collection with several rows, several columns, and is not hierarchical;
+ * A vertical tree is a hierarchical collection with one column and
+ * as many rows as the first level children.
+ * </p>
*/
public static final class CollectionInfo {
private static final int MAX_POOL_SIZE = 20;
@@ -2685,54 +2703,54 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final SynchronizedPool<CollectionInfo> sPool =
new SynchronizedPool<CollectionInfo>(MAX_POOL_SIZE);
- private int mHorizontalSize;
- private int mVerticalSize;
+ private int mRowCount;
+ private int mColumnCount;
private boolean mHierarchical;
/**
* Obtains a pooled instance.
*
- * @param horizontalSize The horizontal size.
- * @param verticalSize The vertical size.
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
* @param hierarchical Whether the collection is hierarchical.
*/
- public static CollectionInfo obtain(int horizontalSize, int verticalSize,
+ public static CollectionInfo obtain(int rowCount, int columnCount,
boolean hierarchical) {
CollectionInfo info = sPool.acquire();
- return (info != null) ? info : new CollectionInfo(horizontalSize,
- verticalSize, hierarchical);
+ return (info != null) ? info : new CollectionInfo(rowCount,
+ columnCount, hierarchical);
}
/**
* Creates a new instance.
*
- * @param horizontalSize The horizontal size.
- * @param verticalSize The vertical size.
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
* @param hierarchical Whether the collection is hierarchical.
*/
- private CollectionInfo(int horizontalSize, int verticalSize,
+ private CollectionInfo(int rowCount, int columnCount,
boolean hierarchical) {
- mHorizontalSize = horizontalSize;
- mVerticalSize = verticalSize;
+ mRowCount = rowCount;
+ mColumnCount = columnCount;
mHierarchical = hierarchical;
}
/**
- * Gets the horizontal size in terms of item positions.
+ * Gets the number of rows.
*
- * @return The size.
+ * @return The row count.
*/
- public int getHorizontalSize() {
- return mHorizontalSize;
+ public int getRowCount() {
+ return mRowCount;
}
/**
- * Gets the vertical size in terms of item positions.
+ * Gets the number of columns.
*
- * @return The size.
+ * @return The column count.
*/
- public int getVerticalSize() {
- return mVerticalSize;
+ public int getColumnCount() {
+ return mColumnCount;
}
/**
@@ -2753,15 +2771,23 @@ public class AccessibilityNodeInfo implements Parcelable {
}
private void clear() {
- mHorizontalSize = 0;
- mVerticalSize = 0;
+ mRowCount = 0;
+ mColumnCount = 0;
mHierarchical = false;
}
}
/**
* Class with information if a node is a collection item. Use
- * {@link CollectionItemInfo#obtain(int, int, int, int, boolean) to get an instance.
+ * {@link CollectionItemInfo#obtain(int, int, int, int, boolean)}
+ * to get an instance.
+ * <p>
+ * A collection item is contained in a collection, it starts at
+ * a given row and column in the collection, and spans one or
+ * more rows and columns. For example, a header of two related
+ * table columns starts at the first row and the first column,
+ * spans one row and two columns.
+ * </p>
*/
public static final class CollectionItemInfo {
private static final int MAX_POOL_SIZE = 20;
@@ -2772,79 +2798,77 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* Obtains a pooled instance.
*
- * @param horizontalPosition The horizontal item position.
- * @param horizontalSpan The horizontal item span.
- * @param verticalPosition The vertical item position.
- * @param verticalSpan The vertical item span.
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
* @param heading Whether the item is a heading.
*/
- public static CollectionItemInfo obtain(int horizontalPosition, int horizontalSpan,
- int verticalPosition, int verticalSpan, boolean heading) {
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading) {
CollectionItemInfo info = sPool.acquire();
- return (info != null) ? info : new CollectionItemInfo(horizontalPosition,
- horizontalSpan, verticalPosition, verticalSpan, heading);
+ return (info != null) ? info : new CollectionItemInfo(rowIndex,
+ rowSpan, columnIndex, columnSpan, heading);
}
private boolean mHeading;
- private int mHorizontalPosition;
- private int mVerticalPosition;
- private int mHorizontalSpan;
- private int mVerticalSpan;
+ private int mColumnIndex;
+ private int mRowIndex;
+ private int mColumnSpan;
+ private int mRowSpan;
/**
* Creates a new instance.
*
- * @param horizontalPosition The horizontal item position.
- * @param horizontalSpan The horizontal item span.
- * @param verticalPosition The vertical item position.
- * @param verticalSpan The vertical item span.
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
* @param heading Whether the item is a heading.
*/
- private CollectionItemInfo(int horizontalPosition, int horizontalSpan,
- int verticalPosition, int verticalSpan, boolean heading) {
- mHorizontalPosition = horizontalPosition;
- mHorizontalSpan = horizontalSpan;
- mVerticalPosition = verticalPosition;
- mVerticalSpan = verticalSpan;
+ private CollectionItemInfo(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading) {
+ mRowIndex = rowIndex;
+ mRowSpan = rowSpan;
+ mColumnIndex = columnIndex;
+ mColumnSpan = columnSpan;
mHeading = heading;
}
/**
- * Gets the horizontal item position in the parent collection.
+ * Gets the column index at which the item is located.
*
- * @return The position.
+ * @return The column index.
*/
- public int getHorizontalPosition() {
- return mHorizontalPosition;
+ public int getColumnIndex() {
+ return mColumnIndex;
}
/**
- * Gets the vertical item position in the parent collection.
+ * Gets the row index at which the item is located.
*
- * @return The position.
+ * @return The row index.
*/
- public int getVerticalPosition() {
- return mVerticalPosition;
+ public int getRowIndex() {
+ return mRowIndex;
}
/**
- * Gets the horizontal span in terms of item positions
- * of the parent collection.
+ * Gets the number of columns the item spans.
*
- * @return The span.
+ * @return The column span.
*/
- public int getHorizontalSpan() {
- return mHorizontalSpan;
+ public int getColumnSpan() {
+ return mColumnSpan;
}
/**
- * Gets the vertical span in terms of item positions
- * of the parent collection.
+ * Gets the number of rows the item spans.
*
- * @return The span.
+ * @return The row span.
*/
- public int getVerticalSpan() {
- return mVerticalSpan;
+ public int getRowSpan() {
+ return mRowSpan;
}
/**
@@ -2866,10 +2890,10 @@ public class AccessibilityNodeInfo implements Parcelable {
}
private void clear() {
- mHorizontalPosition = 0;
- mHorizontalSpan = 0;
- mVerticalPosition = 0;
- mVerticalSpan = 0;
+ mColumnIndex = 0;
+ mColumnSpan = 0;
+ mRowIndex = 0;
+ mRowSpan = 0;
mHeading = false;
}
}
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index b1be24c..d4c6abe 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -17,58 +17,77 @@
package android.view.accessibility;
import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Handler;
import android.provider.Settings.Secure;
import android.text.TextUtils;
+import java.util.ArrayList;
import java.util.Locale;
/**
- * Contains methods for accessing preferred video captioning state and
+ * Contains methods for accessing and monitoring preferred video captioning state and visual
* properties.
+ * <p>
+ * To obtain a handle to the captioning manager, do the following:
+ * <p>
+ * <code>
+ * <pre>CaptioningManager captioningManager =
+ * (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);</pre>
+ * </code>
*/
public class CaptioningManager {
- /**
- * Activity Action: Show settings for video captioning.
- * <p>
- * In some cases, a matching Activity may not exist, so ensure you safeguard
- * against this.
- * <p>
- * Input: Nothing.
- * <p>
- * Output: Nothing.
- */
- public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+ /** Default captioning enabled value. */
+ private static final int DEFAULT_ENABLED = 0;
+ /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
private static final int DEFAULT_PRESET = 0;
- private static final int DEFAULT_ENABLED = 0;
- private static final float DEFAULT_FONT_SIZE = 24;
+
+ /** Default scaling value for caption fonts. */
+ private static final float DEFAULT_FONT_SCALE = 1;
+
+ private final ArrayList<CaptioningChangeListener>
+ mListeners = new ArrayList<CaptioningChangeListener>();
+ private final Handler mHandler = new Handler();
+
+ private final ContentResolver mContentResolver;
+
+ /**
+ * Creates a new captioning manager for the specified context.
+ *
+ * @hide
+ */
+ public CaptioningManager(Context context) {
+ mContentResolver = context.getContentResolver();
+ }
/**
- * @param cr Resolver to access the database with.
- * @return The user's preferred caption enabled state.
+ * @return the user's preferred captioning enabled state
*/
- public static final boolean isEnabled(ContentResolver cr) {
- return Secure.getInt(cr, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
+ public final boolean isEnabled() {
+ return Secure.getInt(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
}
/**
- * @param cr Resolver to access the database with.
- * @return The raw locale string for the user's preferred caption language.
+ * @return the raw locale string for the user's preferred captioning
+ * language
* @hide
*/
- public static final String getRawLocale(ContentResolver cr) {
- return Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+ public final String getRawLocale() {
+ return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
}
/**
- * @param cr Resolver to access the database with.
- * @return The locale for the user's preferred caption language, or null if
- * not specified.
+ * @return the locale for the user's preferred captioning language, or null
+ * if not specified
*/
- public static final Locale getLocale(ContentResolver cr) {
- final String rawLocale = getRawLocale(cr);
+ public final Locale getLocale() {
+ final String rawLocale = getRawLocale();
if (!TextUtils.isEmpty(rawLocale)) {
final String[] splitLocale = rawLocale.split("_");
switch (splitLocale.length) {
@@ -85,14 +104,151 @@ public class CaptioningManager {
}
/**
- * @param cr Resolver to access the database with.
- * @return The user's preferred font size for video captions, or 0 if not
- * specified.
+ * @return the user's preferred font scaling factor for video captions, or 1 if not
+ * specified
*/
- public static final float getFontSize(ContentResolver cr) {
- return Secure.getFloat(cr, Secure.ACCESSIBILITY_CAPTIONING_FONT_SIZE, DEFAULT_FONT_SIZE);
+ public final float getFontScale() {
+ return Secure.getFloat(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, DEFAULT_FONT_SCALE);
+ }
+
+ /**
+ * @return the raw preset number, or the first preset if not specified
+ * @hide
+ */
+ public int getRawUserStyle() {
+ return Secure.getInt(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
+ }
+
+ /**
+ * @return the user's preferred visual properties for captions as a
+ * {@link CaptionStyle}, or the default style if not specified
+ */
+ public CaptionStyle getUserStyle() {
+ final int preset = getRawUserStyle();
+ if (preset == CaptionStyle.PRESET_CUSTOM) {
+ return CaptionStyle.getCustomStyle(mContentResolver);
+ }
+
+ return CaptionStyle.PRESETS[preset];
+ }
+
+ /**
+ * Adds a listener for changes in the user's preferred captioning enabled
+ * state and visual properties.
+ *
+ * @param listener the listener to add
+ */
+ public void addCaptioningStateChangeListener(CaptioningChangeListener listener) {
+ synchronized (mListeners) {
+ if (mListeners.isEmpty()) {
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+ }
+
+ mListeners.add(listener);
+ }
+ }
+
+ private void registerObserver(String key) {
+ mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver);
+ }
+
+ /**
+ * Removes a listener previously added using
+ * {@link #addCaptioningStateChangeListener}.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeCaptioningStateChangeListener(CaptioningChangeListener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+
+ if (mListeners.isEmpty()) {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+ }
+
+ private void notifyEnabledChanged() {
+ final boolean enabled = isEnabled();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onEnabledChanged(enabled);
+ }
+ }
+ }
+
+ private void notifyUserStyleChanged() {
+ final CaptionStyle userStyle = getUserStyle();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onUserStyleChanged(userStyle);
+ }
+ }
}
+ private void notifyLocaleChanged() {
+ final Locale locale = getLocale();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onLocaleChanged(locale);
+ }
+ }
+ }
+
+ private void notifyFontScaleChanged() {
+ final float fontScale = getFontScale();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onFontScaleChanged(fontScale);
+ }
+ }
+ }
+
+ private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final String uriPath = uri.getPath();
+ final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
+ if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) {
+ notifyEnabledChanged();
+ } else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) {
+ notifyLocaleChanged();
+ } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
+ notifyFontScaleChanged();
+ } else {
+ // We only need a single callback when multiple style properties
+ // change in rapid succession.
+ mHandler.removeCallbacks(mStyleChangedRunnable);
+ mHandler.post(mStyleChangedRunnable);
+ }
+ }
+ };
+
+ /**
+ * Runnable posted when user style properties change. This is used to
+ * prevent unnecessary change notifications when multiple properties change
+ * in rapid succession.
+ */
+ private final Runnable mStyleChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyUserStyleChanged();
+ }
+ };
+
+ /**
+ * Specifies visual properties for video captions, including foreground and
+ * background colors, edge properties, and typeface.
+ */
public static final class CaptionStyle {
private static final CaptionStyle WHITE_ON_BLACK;
private static final CaptionStyle BLACK_ON_WHITE;
@@ -155,8 +311,8 @@ public class CaptioningManager {
}
/**
- * @return The preferred {@link Typeface} for video captions, or null if
- * not specified.
+ * @return the preferred {@link Typeface} for video captions, or null if
+ * not specified
*/
public Typeface getTypeface() {
if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
@@ -168,41 +324,20 @@ public class CaptioningManager {
/**
* @hide
*/
- public static int getRawPreset(ContentResolver cr) {
- return Secure.getInt(cr, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
- }
-
- /**
- * @param cr Resolver to access the database with.
- * @return The user's preferred caption style.
- */
- public static CaptionStyle defaultUserStyle(ContentResolver cr) {
- final int preset = getRawPreset(cr);
- if (preset == PRESET_CUSTOM) {
- return getCustomStyle(cr);
- }
-
- return PRESETS[preset];
- }
-
- /**
- * @hide
- */
public static CaptionStyle getCustomStyle(ContentResolver cr) {
+ final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
final int foregroundColor = Secure.getInt(
- cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
- DEFAULT_CUSTOM.foregroundColor);
- final int backgroundColor = Secure.getInt(cr,
- Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
- DEFAULT_CUSTOM.backgroundColor);
+ cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
+ final int backgroundColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
final int edgeType = Secure.getInt(
- cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, DEFAULT_CUSTOM.edgeType);
+ cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
final int edgeColor = Secure.getInt(
- cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, DEFAULT_CUSTOM.edgeColor);
+ cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
if (rawTypeface == null) {
- rawTypeface = DEFAULT_CUSTOM.mRawTypeface;
+ rawTypeface = defStyle.mRawTypeface;
}
return new CaptionStyle(
@@ -226,4 +361,45 @@ public class CaptioningManager {
DEFAULT_CUSTOM = WHITE_ON_BLACK;
}
}
+
+ /**
+ * Listener for changes in captioning properties, including enabled state
+ * and user style preferences.
+ */
+ public abstract class CaptioningChangeListener {
+ /**
+ * Called when the captioning enabled state changes.
+ *
+ * @param enabled the user's new preferred captioning enabled state
+ */
+ public void onEnabledChanged(boolean enabled) {
+ }
+
+ /**
+ * Called when the captioning user style changes.
+ *
+ * @param userStyle the user's new preferred style
+ * @see CaptioningManager#getUserStyle()
+ */
+ public void onUserStyleChanged(CaptionStyle userStyle) {
+ }
+
+ /**
+ * Called when the captioning locale changes.
+ *
+ * @param locale the preferred captioning locale
+ * @see CaptioningManager#getLocale()
+ */
+ public void onLocaleChanged(Locale locale) {
+ }
+
+ /**
+ * Called when the captioning font scaling factor changes.
+ *
+ * @param fontScale the preferred font scaling factor
+ * @see CaptioningManager#getFontScale()
+ */
+ public void onFontScaleChanged(float fontScale) {
+ }
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f97e3dd..54b87de 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1876,9 +1876,13 @@ public final class InputMethodManager {
}
/**
- * Returns true if the current IME needs to offer the users a way to switch to a next input
- * method. When the user triggers it, the IME has to call {@link #switchToNextInputMethod} to
- * switch to a next input method which is selected by the system.
+ * Returns true if the current IME needs to offer the users ways to switch to a next input
+ * method (e.g. a globe key.).
+ * When an IME sets supportsSwitchingToNextInputMethod and this method returns true,
+ * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly.
+ * <p> Note that the system determines the most appropriate next input method
+ * and subtype in order to provide the consistent user experience in switching
+ * between IMEs and subtypes.
* @param imeToken Supplies the identifying token given to an input method when it was started,
* which allows it to perform this operation on itself.
*/
diff --git a/core/java/android/view/transition/Move.java b/core/java/android/view/transition/Move.java
index ae7d759..fda0cd2 100644
--- a/core/java/android/view/transition/Move.java
+++ b/core/java/android/view/transition/Move.java
@@ -40,7 +40,7 @@ public class Move extends Transition {
private static final String PROPNAME_PARENT = "android:move:parent";
private static final String PROPNAME_WINDOW_X = "android:move:windowX";
private static final String PROPNAME_WINDOW_Y = "android:move:windowY";
- private static String[] sTransitionProperties = {
+ private static final String[] sTransitionProperties = {
PROPNAME_BOUNDS,
PROPNAME_PARENT,
PROPNAME_WINDOW_X,
diff --git a/core/java/android/view/transition/TextChange.java b/core/java/android/view/transition/TextChange.java
index 04ff707..7973c97 100644
--- a/core/java/android/view/transition/TextChange.java
+++ b/core/java/android/view/transition/TextChange.java
@@ -78,6 +78,10 @@ public class TextChange extends Transition {
*/
public static final int CHANGE_BEHAVIOR_OUT_IN = 3;
+ private static final String[] sTransitionProperties = {
+ PROPNAME_TEXT
+ };
+
/**
* Sets the type of changing animation that will be run, one of
* {@link #CHANGE_BEHAVIOR_KEEP} and {@link #CHANGE_BEHAVIOR_OUT_IN}.
@@ -92,6 +96,11 @@ public class TextChange extends Transition {
}
@Override
+ public String[] getTransitionProperties() {
+ return sTransitionProperties;
+ }
+
+ @Override
protected void captureValues(TransitionValues values, boolean start) {
if (values.view instanceof TextView) {
TextView textview = (TextView) values.view;
@@ -111,7 +120,7 @@ public class TextChange extends Transition {
final TextView view = (TextView) endValues.view;
Map<String, Object> startVals = startValues.values;
Map<String, Object> endVals = endValues.values;
- String startText = (String) startVals.get(PROPNAME_TEXT);
+ final String startText = (String) startVals.get(PROPNAME_TEXT);
final String endText = (String) endVals.get(PROPNAME_TEXT);
if (!startText.equals(endText)) {
view.setText(startText);
@@ -121,7 +130,10 @@ public class TextChange extends Transition {
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setText(endText);
+ if (startText.equals(view.getText())) {
+ // Only set if it hasn't been changed since anim started
+ view.setText(endText);
+ }
}
});
} else {
@@ -143,7 +155,10 @@ public class TextChange extends Transition {
outAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setText(endText);
+ if (startText.equals(view.getText())) {
+ // Only set if it hasn't been changed since anim started
+ view.setText(endText);
+ }
}
});
}
@@ -169,6 +184,20 @@ public class TextChange extends Transition {
anim = inAnim;
}
}
+ TransitionListener transitionListener = new TransitionListenerAdapter() {
+ boolean mCanceled = false;
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ view.setText(endText);
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ view.setText(startText);
+ }
+ };
+ addListener(transitionListener);
return anim;
}
return null;
diff --git a/core/java/android/view/transition/Transition.java b/core/java/android/view/transition/Transition.java
index 0444843..a66fa52 100644
--- a/core/java/android/view/transition/Transition.java
+++ b/core/java/android/view/transition/Transition.java
@@ -843,7 +843,6 @@ public abstract class Transition implements Cloneable {
for (int i = numOldAnims - 1; i >= 0; i--) {
Animator anim = runningAnimators.keyAt(i);
if (anim != null) {
- anim.resume();
AnimationInfo oldInfo = runningAnimators.get(anim);
if (oldInfo != null) {
boolean cancel = false;
@@ -851,22 +850,25 @@ public abstract class Transition implements Cloneable {
View oldView = oldInfo.view;
TransitionValues newValues = mEndValues.viewValues != null ?
mEndValues.viewValues.get(oldView) : null;
- if (oldValues == null || newValues == null) {
- if (oldValues != null || newValues != null) {
+ if (oldValues != null) {
+ // if oldValues null, then transition didn't care to stash values,
+ // and won't get canceled
+ if (newValues == null) {
cancel = true;
- }
- } else {
- for (String key : oldValues.values.keySet()) {
- Object oldValue = oldValues.values.get(key);
- Object newValue = newValues.values.get(key);
- if ((oldValue == null && newValue != null) ||
- (oldValue != null && !oldValue.equals(newValue))) {
- cancel = true;
- if (DBG) {
- Log.d(LOG_TAG, "Transition.play: oldValue != newValue for " +
- key + ": old, new = " + oldValue + ", " + newValue);
+ } else {
+ for (String key : oldValues.values.keySet()) {
+ Object oldValue = oldValues.values.get(key);
+ Object newValue = newValues.values.get(key);
+ if (oldValue != null && newValue != null &&
+ !oldValue.equals(newValue)) {
+ cancel = true;
+ if (DBG) {
+ Log.d(LOG_TAG, "Transition.playTransition: " +
+ "oldValue != newValue for " + key +
+ ": old, new = " + oldValue + ", " + newValue);
+ }
+ break;
}
- break;
}
}
}
diff --git a/core/java/android/view/transition/TransitionManager.java b/core/java/android/view/transition/TransitionManager.java
index 3cb6f68..bde891d 100644
--- a/core/java/android/view/transition/TransitionManager.java
+++ b/core/java/android/view/transition/TransitionManager.java
@@ -183,9 +183,12 @@ public class TransitionManager {
final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
getRunningTransitions();
ArrayList<Transition> currentTransitions = runningTransitions.get(sceneRoot);
+ ArrayList<Transition> previousRunningTransitions = null;
if (currentTransitions == null) {
currentTransitions = new ArrayList<Transition>();
runningTransitions.put(sceneRoot, currentTransitions);
+ } else if (currentTransitions.size() > 0) {
+ previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
}
currentTransitions.add(transition);
transition.addListener(new Transition.TransitionListenerAdapter() {
@@ -197,6 +200,11 @@ public class TransitionManager {
}
});
transition.captureValues(sceneRoot, false);
+ if (previousRunningTransitions != null) {
+ for (Transition runningTransition : previousRunningTransitions) {
+ runningTransition.resume();
+ }
+ }
transition.playTransition(sceneRoot);
// Returning false from onPreDraw() skips the current frame. This is
diff --git a/core/java/android/view/transition/Visibility.java b/core/java/android/view/transition/Visibility.java
index 96ea044..348dcfb 100644
--- a/core/java/android/view/transition/Visibility.java
+++ b/core/java/android/view/transition/Visibility.java
@@ -29,17 +29,31 @@ 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 {
private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
private static final String PROPNAME_PARENT = "android:visibility:parent";
- private static String[] sTransitionProperties = {
+ private static final String[] sTransitionProperties = {
PROPNAME_VISIBILITY,
PROPNAME_PARENT,
};
@@ -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/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index fea6be6..7707392 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -50,7 +50,9 @@ import java.util.Map;
*/
class CallbackProxy extends Handler {
// Logging tag
- private static final String LOGTAG = "CallbackProxy";
+ static final String LOGTAG = "WebViewCallback";
+ // Enables API callback tracing
+ private static final boolean TRACE = DebugFlags.TRACE_CALLBACK;
// Instance of WebViewClient that is the client callback.
private volatile WebViewClient mWebViewClient;
// Instance of WebChromeClient for handling all chrome functions.
@@ -258,6 +260,7 @@ class CallbackProxy extends Handler {
}
boolean override = false;
if (mWebViewClient != null) {
+ if (TRACE) Log.d(LOGTAG, "shouldOverrideUrlLoading=" + overrideUrl);
override = mWebViewClient.shouldOverrideUrlLoading(mWebView.getWebView(),
overrideUrl);
} else {
@@ -307,6 +310,7 @@ class CallbackProxy extends Handler {
String startedUrl = msg.getData().getString("url");
mWebView.onPageStarted(startedUrl);
if (mWebViewClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onPageStarted=" + startedUrl);
mWebViewClient.onPageStarted(mWebView.getWebView(), startedUrl,
(Bitmap) msg.obj);
}
@@ -316,18 +320,21 @@ class CallbackProxy extends Handler {
String finishedUrl = (String) msg.obj;
mWebView.onPageFinished(finishedUrl);
if (mWebViewClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onPageFinished=" + finishedUrl);
mWebViewClient.onPageFinished(mWebView.getWebView(), finishedUrl);
}
break;
case RECEIVED_ICON:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onReceivedIcon");
mWebChromeClient.onReceivedIcon(mWebView.getWebView(), (Bitmap) msg.obj);
}
break;
case RECEIVED_TOUCH_ICON_URL:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onReceivedTouchIconUrl");
mWebChromeClient.onReceivedTouchIconUrl(mWebView.getWebView(),
(String) msg.obj, msg.arg1 == 1);
}
@@ -335,6 +342,7 @@ class CallbackProxy extends Handler {
case RECEIVED_TITLE:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onReceivedTitle");
mWebChromeClient.onReceivedTitle(mWebView.getWebView(),
(String) msg.obj);
}
@@ -345,6 +353,7 @@ class CallbackProxy extends Handler {
int reasonCode = msg.arg1;
final String description = msg.getData().getString("description");
final String failUrl = msg.getData().getString("failingUrl");
+ if (TRACE) Log.d(LOGTAG, "onReceivedError=" + failUrl);
mWebViewClient.onReceivedError(mWebView.getWebView(), reasonCode,
description, failUrl);
}
@@ -356,6 +365,7 @@ class CallbackProxy extends Handler {
Message dontResend =
(Message) msg.getData().getParcelable("dontResend");
if (mWebViewClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onFormResubmission");
mWebViewClient.onFormResubmission(mWebView.getWebView(), dontResend,
resend);
} else {
@@ -379,6 +389,7 @@ class CallbackProxy extends Handler {
HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
String host = msg.getData().getString("host");
String realm = msg.getData().getString("realm");
+ if (TRACE) Log.d(LOGTAG, "onReceivedHttpAuthRequest");
mWebViewClient.onReceivedHttpAuthRequest(mWebView.getWebView(), handler,
host, realm);
}
@@ -388,6 +399,7 @@ class CallbackProxy extends Handler {
if (mWebViewClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
+ if (TRACE) Log.d(LOGTAG, "onReceivedSslError");
mWebViewClient.onReceivedSslError(mWebView.getWebView(),
(SslErrorHandler) map.get("handler"),
(SslError) map.get("error"));
@@ -396,6 +408,7 @@ class CallbackProxy extends Handler {
case PROCEEDED_AFTER_SSL_ERROR:
if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
+ if (TRACE) Log.d(LOGTAG, "onProceededAfterSslError");
((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
mWebView.getWebView(),
(SslError) msg.obj);
@@ -404,6 +417,7 @@ class CallbackProxy extends Handler {
case CLIENT_CERT_REQUEST:
if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
+ if (TRACE) Log.d(LOGTAG, "onReceivedClientCertRequest");
HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
mWebView.getWebView(),
@@ -418,6 +432,7 @@ class CallbackProxy extends Handler {
// changed.
synchronized (this) {
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onProgressChanged=" + mLatestProgress);
mWebChromeClient.onProgressChanged(mWebView.getWebView(),
mLatestProgress);
}
@@ -427,14 +442,18 @@ class CallbackProxy extends Handler {
case UPDATE_VISITED:
if (mWebViewClient != null) {
+ String url = (String) msg.obj;
+ if (TRACE) Log.d(LOGTAG, "doUpdateVisitedHistory=" + url);
mWebViewClient.doUpdateVisitedHistory(mWebView.getWebView(),
- (String) msg.obj, msg.arg1 != 0);
+ url, msg.arg1 != 0);
}
break;
case LOAD_RESOURCE:
if (mWebViewClient != null) {
- mWebViewClient.onLoadResource(mWebView.getWebView(), (String) msg.obj);
+ String url = (String) msg.obj;
+ if (TRACE) Log.d(LOGTAG, "onLoadResource=" + url);
+ mWebViewClient.onLoadResource(mWebView.getWebView(), url);
}
break;
@@ -448,6 +467,7 @@ class CallbackProxy extends Handler {
String referer = msg.getData().getString("referer");
Long contentLength = msg.getData().getLong("contentLength");
+ if (TRACE) Log.d(LOGTAG, "onDownloadStart");
if (mDownloadListener instanceof BrowserDownloadListener) {
((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
userAgent, contentDisposition, mimetype, referer, contentLength);
@@ -460,6 +480,7 @@ class CallbackProxy extends Handler {
case CREATE_WINDOW:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onCreateWindow");
if (!mWebChromeClient.onCreateWindow(mWebView.getWebView(),
msg.arg1 == 1, msg.arg2 == 1,
(Message) msg.obj)) {
@@ -473,12 +494,14 @@ class CallbackProxy extends Handler {
case REQUEST_FOCUS:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onRequestFocus");
mWebChromeClient.onRequestFocus(mWebView.getWebView());
}
break;
case CLOSE_WINDOW:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onCloseWindow");
mWebChromeClient.onCloseWindow(((WebViewClassic) msg.obj).getWebView());
}
break;
@@ -500,6 +523,7 @@ class CallbackProxy extends Handler {
case ASYNC_KEYEVENTS:
if (mWebViewClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onUnhandledKeyEvent");
mWebViewClient.onUnhandledKeyEvent(mWebView.getWebView(),
(KeyEvent) msg.obj);
}
@@ -521,6 +545,7 @@ class CallbackProxy extends Handler {
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
+ if (TRACE) Log.d(LOGTAG, "onExceededDatabaseQuota");
mWebChromeClient.onExceededDatabaseQuota(url,
databaseIdentifier, quota, estimatedDatabaseSize,
totalQuota, quotaUpdater);
@@ -538,6 +563,7 @@ class CallbackProxy extends Handler {
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
+ if (TRACE) Log.d(LOGTAG, "onReachedMaxAppCacheSize");
mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
quota, quotaUpdater);
}
@@ -551,6 +577,7 @@ class CallbackProxy extends Handler {
GeolocationPermissions.Callback callback =
(GeolocationPermissions.Callback)
map.get("callback");
+ if (TRACE) Log.d(LOGTAG, "onGeolocationPermissionsShowPrompt");
mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
callback);
}
@@ -558,6 +585,7 @@ class CallbackProxy extends Handler {
case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onGeolocationPermissionsHidePrompt");
mWebChromeClient.onGeolocationPermissionsHidePrompt();
}
break;
@@ -566,6 +594,7 @@ class CallbackProxy extends Handler {
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
+ if (TRACE) Log.d(LOGTAG, "onJsAlert");
if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
helper.showDialog(mContext);
}
@@ -577,6 +606,7 @@ class CallbackProxy extends Handler {
if(mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
+ if (TRACE) Log.d(LOGTAG, "onJsTimeout");
if (mWebChromeClient.onJsTimeout()) {
res.confirm();
} else {
@@ -598,6 +628,7 @@ class CallbackProxy extends Handler {
case SCALE_CHANGED:
if (mWebViewClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onScaleChanged");
mWebViewClient.onScaleChanged(mWebView.getWebView(), msg.getData()
.getFloat("old"), msg.getData().getFloat("new"));
}
@@ -624,6 +655,7 @@ class CallbackProxy extends Handler {
ConsoleMessage.MessageLevel messageLevel =
ConsoleMessage.MessageLevel.values()[msgLevel];
+ if (TRACE) Log.d(LOGTAG, "onConsoleMessage");
if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
lineNumber, messageLevel))) {
// If false was returned the user did not provide their own console function so
@@ -654,12 +686,14 @@ class CallbackProxy extends Handler {
case GET_VISITED_HISTORY:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "getVisitedHistory");
mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
}
break;
case OPEN_FILE_CHOOSER:
if (mWebChromeClient != null) {
+ if (TRACE) Log.d(LOGTAG, "openFileChooser");
UploadFileMessageData data = (UploadFileMessageData)msg.obj;
mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
data.getCapture());
@@ -668,6 +702,7 @@ class CallbackProxy extends Handler {
case ADD_HISTORY_ITEM:
if (mWebBackForwardListClient != null) {
+ if (TRACE) Log.d(LOGTAG, "onNewHistoryItem");
mWebBackForwardListClient.onNewHistoryItem(
(WebHistoryItem) msg.obj);
}
@@ -693,6 +728,7 @@ class CallbackProxy extends Handler {
String realm = msg.getData().getString("realm");
String account = msg.getData().getString("account");
String args = msg.getData().getString("args");
+ if (TRACE) Log.d(LOGTAG, "onReceivedLoginRequest");
mWebViewClient.onReceivedLoginRequest(mWebView.getWebView(), realm,
account, args);
}
@@ -910,6 +946,7 @@ class CallbackProxy extends Handler {
return null;
}
// Note: This method does _not_ send a message.
+ if (TRACE) Log.d(LOGTAG, "shouldInterceptRequest=" + url);
WebResourceResponse r =
mWebViewClient.shouldInterceptRequest(mWebView.getWebView(), url);
if (r == null) {
diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index 349113e..524f610 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -24,25 +24,33 @@ package android.webkit;
* The name of each flags maps directly to the name of the class in which that
* flag is used.
*
+ * @hide Only used by WebView implementations.
*/
-class DebugFlags {
+public class DebugFlags {
+ public static final boolean COOKIE_SYNC_MANAGER = false;
+ public static final boolean TRACE_API = false;
+ public static final boolean TRACE_CALLBACK = false;
+ public static final boolean TRACE_JAVASCRIPT_BRIDGE = false;
+ public static final boolean URL_UTIL = false;
+ public static final boolean WEB_SYNC_MANAGER = false;
+
+ // TODO: Delete these when WebViewClassic is moved
public static final boolean BROWSER_FRAME = false;
public static final boolean CACHE_MANAGER = false;
public static final boolean CALLBACK_PROXY = false;
public static final boolean COOKIE_MANAGER = false;
- public static final boolean COOKIE_SYNC_MANAGER = false;
public static final boolean FRAME_LOADER = false;
public static final boolean J_WEB_CORE_JAVA_BRIDGE = false;// HIGHLY VERBOSE
public static final boolean LOAD_LISTENER = false;
+ public static final boolean MEASURE_PAGE_SWAP_FPS = false;
public static final boolean NETWORK = false;
public static final boolean SSL_ERROR_HANDLER = false;
public static final boolean STREAM_LOADER = false;
- public static final boolean URL_UTIL = false;
public static final boolean WEB_BACK_FORWARD_LIST = false;
public static final boolean WEB_SETTINGS = false;
- public static final boolean WEB_SYNC_MANAGER = false;
public static final boolean WEB_VIEW = false;
public static final boolean WEB_VIEW_CORE = false;
- public static final boolean MEASURE_PAGE_SWAP_FPS = false;
+
+
}
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index b52218d..6fb32c8 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -19,6 +19,7 @@ package android.webkit;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.Metadata;
+import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
@@ -293,12 +294,16 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mLayout.setVisibility(View.VISIBLE);
WebChromeClient client = webView.getWebChromeClient();
if (client != null) {
+ if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onShowCustomView");
client.onShowCustomView(mLayout, mCallback);
// Plugins like Flash will draw over the video so hide
// them while we're playing.
if (webView.getViewManager() != null)
webView.getViewManager().hideAll();
+ if (DebugFlags.TRACE_CALLBACK) {
+ Log.d(CallbackProxy.LOGTAG, "getVideoLoadingProgressView");
+ }
mProgressView = client.getVideoLoadingProgressView();
if (mProgressView != null) {
mLayout.addView(mProgressView, layoutParams);
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index a3d62ae..e8538f6 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -180,6 +180,7 @@ class HTML5VideoViewProxy extends Handler
if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
WebChromeClient client = webView.getWebChromeClient();
if (client != null) {
+ if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onHideCustomView");
client.onHideCustomView();
}
}
@@ -405,6 +406,7 @@ class HTML5VideoViewProxy extends Handler
case ERROR: {
WebChromeClient client = mWebView.getWebChromeClient();
if (client != null) {
+ if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onHideCustomView");
client.onHideCustomView();
}
break;
@@ -412,6 +414,9 @@ class HTML5VideoViewProxy extends Handler
case LOAD_DEFAULT_POSTER: {
WebChromeClient client = mWebView.getWebChromeClient();
if (client != null) {
+ if (DebugFlags.TRACE_CALLBACK) {
+ Log.d(CallbackProxy.LOGTAG, "getDefaultVideoPoster");
+ }
doSetPoster(client.getDefaultVideoPoster());
}
break;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 1b57d50..f0e8c4f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -31,7 +31,9 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.StrictMode;
+import android.print.PrintAttributes;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -49,7 +51,6 @@ import android.widget.AbsoluteLayout;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.OutputStream;
import java.util.Map;
/**
@@ -243,7 +244,7 @@ public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
- private static final String LOGTAG = "webview_proxy";
+ private static final String LOGTAG = "WebView";
// Throwing an exception for incorrect thread usage if the
// build target is JB MR2 or newer. Defaults to false, and is
@@ -495,9 +496,12 @@ public class WebView extends AbsoluteLayout
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "WebView<init>");
ensureProviderCreated();
mProvider.init(javaScriptInterfaces, privateBrowsing);
+ // Post condition of creating a webview is the CookieSyncManager instance exists.
+ CookieSyncManager.createInstance(getContext());
}
/**
@@ -507,6 +511,7 @@ public class WebView extends AbsoluteLayout
*/
public void setHorizontalScrollbarOverlay(boolean overlay) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setHorizontalScrollbarOverlay=" + overlay);
mProvider.setHorizontalScrollbarOverlay(overlay);
}
@@ -517,6 +522,7 @@ public class WebView extends AbsoluteLayout
*/
public void setVerticalScrollbarOverlay(boolean overlay) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setVerticalScrollbarOverlay=" + overlay);
mProvider.setVerticalScrollbarOverlay(overlay);
}
@@ -571,6 +577,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public void setCertificate(SslCertificate certificate) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setCertificate=" + certificate);
mProvider.setCertificate(certificate);
}
@@ -594,6 +601,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public void savePassword(String host, String username, String password) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "savePassword=" + host);
mProvider.savePassword(host, username, password);
}
@@ -613,6 +621,7 @@ public class WebView extends AbsoluteLayout
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setHttpAuthUsernamePassword=" + host);
mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
}
@@ -642,6 +651,7 @@ public class WebView extends AbsoluteLayout
*/
public void destroy() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "destroy");
mProvider.destroy();
}
@@ -680,6 +690,7 @@ public class WebView extends AbsoluteLayout
*/
public void setNetworkAvailable(boolean networkUp) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setNetworkAvailable=" + networkUp);
mProvider.setNetworkAvailable(networkUp);
}
@@ -696,6 +707,7 @@ public class WebView extends AbsoluteLayout
*/
public WebBackForwardList saveState(Bundle outState) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "saveState");
return mProvider.saveState(outState);
}
@@ -712,6 +724,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public boolean savePicture(Bundle b, final File dest) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "savePicture=" + dest.getName());
return mProvider.savePicture(b, dest);
}
@@ -729,6 +742,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public boolean restorePicture(Bundle b, File src) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "restorePicture=" + src.getName());
return mProvider.restorePicture(b, src);
}
@@ -746,6 +760,7 @@ public class WebView extends AbsoluteLayout
*/
public WebBackForwardList restoreState(Bundle inState) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "restoreState");
return mProvider.restoreState(inState);
}
@@ -762,6 +777,7 @@ public class WebView extends AbsoluteLayout
*/
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadUrl(extra headers)=" + url);
mProvider.loadUrl(url, additionalHttpHeaders);
}
@@ -772,6 +788,7 @@ public class WebView extends AbsoluteLayout
*/
public void loadUrl(String url) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadUrl=" + url);
mProvider.loadUrl(url);
}
@@ -786,6 +803,7 @@ public class WebView extends AbsoluteLayout
*/
public void postUrl(String url, byte[] postData) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "postUrl=" + url);
mProvider.postUrl(url, postData);
}
@@ -820,6 +838,7 @@ public class WebView extends AbsoluteLayout
*/
public void loadData(String data, String mimeType, String encoding) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadData");
mProvider.loadData(data, mimeType, encoding);
}
@@ -852,6 +871,7 @@ public class WebView extends AbsoluteLayout
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadDataWithBaseURL=" + baseUrl);
mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
@@ -868,6 +888,7 @@ public class WebView extends AbsoluteLayout
*/
public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "evaluateJavascript=" + script);
mProvider.evaluateJavaScript(script, resultCallback);
}
@@ -878,6 +899,7 @@ public class WebView extends AbsoluteLayout
*/
public void saveWebArchive(String filename) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "saveWebArchive=" + filename);
mProvider.saveWebArchive(filename);
}
@@ -895,6 +917,7 @@ public class WebView extends AbsoluteLayout
*/
public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "saveWebArchive(auto)=" + basename);
mProvider.saveWebArchive(basename, autoname, callback);
}
@@ -903,6 +926,7 @@ public class WebView extends AbsoluteLayout
*/
public void stopLoading() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "stopLoading");
mProvider.stopLoading();
}
@@ -911,6 +935,7 @@ public class WebView extends AbsoluteLayout
*/
public void reload() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "reload");
mProvider.reload();
}
@@ -929,6 +954,7 @@ public class WebView extends AbsoluteLayout
*/
public void goBack() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "goBack");
mProvider.goBack();
}
@@ -947,6 +973,7 @@ public class WebView extends AbsoluteLayout
*/
public void goForward() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "goForward");
mProvider.goForward();
}
@@ -972,6 +999,7 @@ public class WebView extends AbsoluteLayout
*/
public void goBackOrForward(int steps) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "goBackOrForwad=" + steps);
mProvider.goBackOrForward(steps);
}
@@ -991,6 +1019,7 @@ public class WebView extends AbsoluteLayout
*/
public boolean pageUp(boolean top) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "pageUp");
return mProvider.pageUp(top);
}
@@ -1002,6 +1031,7 @@ public class WebView extends AbsoluteLayout
*/
public boolean pageDown(boolean bottom) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "pageDown");
return mProvider.pageDown(bottom);
}
@@ -1014,6 +1044,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public void clearView() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearView");
mProvider.clearView();
}
@@ -1033,6 +1064,7 @@ public class WebView extends AbsoluteLayout
*/
public Picture capturePicture() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "capturePicture");
return mProvider.capturePicture();
}
@@ -1040,7 +1072,9 @@ public class WebView extends AbsoluteLayout
* Exports the contents of this Webview as PDF. Only supported for API levels
* {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE} and above.
*
- * @param out The stream to export the PDF contents to. Cannot be null.
+ * TODO(sgurun) the parameter list is stale. Fix it before unhiding.
+ *
+ * @param fd The FileDescriptor to export the PDF contents to. Cannot be null.
* @param width The page width. Should be larger than 0.
* @param height The page height. Should be larger than 0.
* @param resultCallback A callback to be invoked when the PDF content is exported.
@@ -1049,21 +1083,27 @@ public class WebView extends AbsoluteLayout
* be null.
*
* The PDF conversion is done asynchronously and the PDF output is written to the provided
- * outputstream. The caller should not close the outputstream until the resultCallback is
- * called, indicating PDF conversion is complete. Webview cannot be drawn during the pdf
- * export so the application is recommended to take it offscreen, or putting in a layer
- * with an overlaid progress UI / spinner.
+ * file descriptor. The caller should not close the file descriptor until the resultCallback
+ * is called, indicating PDF conversion is complete. Webview will never close the file
+ * descriptor.
+ * Limitations: Webview cannot be drawn during the PDF export so the application is
+ * recommended to take it offscreen, or putting in a layer with an overlaid progress
+ * UI / spinner.
*
* If the caller cancels the task using the cancellationSignal, the cancellation will be
* acked using the resultCallback signal.
*
+ * Throws an exception if an IO error occurs accessing the file descriptor.
+ *
* TODO(sgurun) margins, explain the units, make it public.
* @hide
*/
- public void exportToPdf(OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal) {
+ public void exportToPdf(ParcelFileDescriptor fd, PrintAttributes attributes,
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal)
+ throws java.io.IOException {
checkThread();
- mProvider.exportToPdf(out, width, height, resultCallback, cancellationSignal);
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "exportToPdf");
+ mProvider.exportToPdf(fd, attributes, resultCallback, cancellationSignal);
}
/**
@@ -1094,6 +1134,7 @@ public class WebView extends AbsoluteLayout
*/
public void setInitialScale(int scaleInPercent) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setInitialScale=" + scaleInPercent);
mProvider.setInitialScale(scaleInPercent);
}
@@ -1104,6 +1145,7 @@ public class WebView extends AbsoluteLayout
*/
public void invokeZoomPicker() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "invokeZoomPicker");
mProvider.invokeZoomPicker();
}
@@ -1127,6 +1169,7 @@ public class WebView extends AbsoluteLayout
*/
public HitTestResult getHitTestResult() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "getHitTestResult");
return mProvider.getHitTestResult();
}
@@ -1145,6 +1188,7 @@ public class WebView extends AbsoluteLayout
*/
public void requestFocusNodeHref(Message hrefMsg) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "requestFocusNodeHref");
mProvider.requestFocusNodeHref(hrefMsg);
}
@@ -1157,6 +1201,7 @@ public class WebView extends AbsoluteLayout
*/
public void requestImageRef(Message msg) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "requestImageRef");
mProvider.requestImageRef(msg);
}
@@ -1261,6 +1306,7 @@ public class WebView extends AbsoluteLayout
*/
public void pauseTimers() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "pauseTimers");
mProvider.pauseTimers();
}
@@ -1270,6 +1316,7 @@ public class WebView extends AbsoluteLayout
*/
public void resumeTimers() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "resumeTimers");
mProvider.resumeTimers();
}
@@ -1282,6 +1329,7 @@ public class WebView extends AbsoluteLayout
*/
public void onPause() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "onPause");
mProvider.onPause();
}
@@ -1290,6 +1338,7 @@ public class WebView extends AbsoluteLayout
*/
public void onResume() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "onResume");
mProvider.onResume();
}
@@ -1309,6 +1358,7 @@ public class WebView extends AbsoluteLayout
*/
public void freeMemory() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "freeMemory");
mProvider.freeMemory();
}
@@ -1320,6 +1370,7 @@ public class WebView extends AbsoluteLayout
*/
public void clearCache(boolean includeDiskFiles) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearCache");
mProvider.clearCache(includeDiskFiles);
}
@@ -1331,6 +1382,7 @@ public class WebView extends AbsoluteLayout
*/
public void clearFormData() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearFormData");
mProvider.clearFormData();
}
@@ -1339,6 +1391,7 @@ public class WebView extends AbsoluteLayout
*/
public void clearHistory() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearHistory");
mProvider.clearHistory();
}
@@ -1348,6 +1401,7 @@ public class WebView extends AbsoluteLayout
*/
public void clearSslPreferences() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearSslPreferences");
mProvider.clearSslPreferences();
}
@@ -1389,6 +1443,7 @@ public class WebView extends AbsoluteLayout
*/
public void findNext(boolean forward) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "findNext");
mProvider.findNext(forward);
}
@@ -1404,6 +1459,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public int findAll(String find) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "findAll");
StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
return mProvider.findAll(find);
}
@@ -1418,6 +1474,7 @@ public class WebView extends AbsoluteLayout
*/
public void findAllAsync(String find) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "findAllAsync");
mProvider.findAllAsync(find);
}
@@ -1438,6 +1495,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public boolean showFindDialog(String text, boolean showIme) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "showFindDialog");
return mProvider.showFindDialog(text, showIme);
}
@@ -1473,6 +1531,7 @@ public class WebView extends AbsoluteLayout
*/
public void clearMatches() {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "clearMatches");
mProvider.clearMatches();
}
@@ -1533,6 +1592,7 @@ public class WebView extends AbsoluteLayout
@Deprecated
public void setPictureListener(PictureListener listener) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "setPictureListener=" + listener);
mProvider.setPictureListener(listener);
}
@@ -1582,6 +1642,7 @@ public class WebView extends AbsoluteLayout
*/
public void addJavascriptInterface(Object object, String name) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "addJavascriptInterface=" + name);
mProvider.addJavascriptInterface(object, name);
}
@@ -1594,6 +1655,7 @@ public class WebView extends AbsoluteLayout
*/
public void removeJavascriptInterface(String name) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "removeJavascriptInterface=" + name);
mProvider.removeJavascriptInterface(name);
}
@@ -1683,6 +1745,7 @@ public class WebView extends AbsoluteLayout
public void flingScroll(int vx, int vy) {
checkThread();
+ if (DebugFlags.TRACE_API) Log.d(LOGTAG, "flingScroll");
mProvider.flingScroll(vx, vy);
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index db98d30..3f22d53 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -62,6 +62,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.print.PrintAttributes;
import android.security.KeyChain;
import android.text.Editable;
import android.text.InputType;
@@ -2896,11 +2897,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* See {@link WebView#exportToPdf()}
*/
@Override
- public void exportToPdf(java.io.OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal) {
+ public void exportToPdf(android.os.ParcelFileDescriptor fd, PrintAttributes attributes,
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal)
+ throws java.io.IOException {
// K-only API not implemented in WebViewClassic.
throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
-
}
/**
@@ -7950,6 +7951,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// triggered in setNewPicture
Picture picture = mContext.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
+ if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onNewPicture");
mPictureListener.onNewPicture(getWebView(), picture);
}
}
@@ -8037,6 +8039,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// triggered in pageSwapCallback
Picture picture = mContext.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
+ if (DebugFlags.TRACE_CALLBACK) Log.d(CallbackProxy.LOGTAG, "onNewPicture");
mPictureListener.onNewPicture(getWebView(), picture);
}
}
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 8fe6edf..d625d8a 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -27,6 +27,8 @@ import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.print.PrintAttributes;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -41,7 +43,6 @@ import android.webkit.WebView.PictureListener;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.OutputStream;
import java.util.Map;
/**
@@ -148,8 +149,9 @@ public interface WebViewProvider {
public Picture capturePicture();
- public void exportToPdf(OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal);
+ public void exportToPdf(ParcelFileDescriptor fd, PrintAttributes attributes,
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal)
+ throws java.io.IOException;
public float getScale();
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 285e6f2..be47bf0 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2309,33 +2309,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
super.onInitializeAccessibilityNodeInfo(host, info);
final int position = getPositionForView(host);
- final ListAdapter adapter = getAdapter();
-
- if ((position == INVALID_POSITION) || (adapter == null)) {
- return;
- }
-
- if (!isEnabled() || !adapter.isEnabled(position)) {
- return;
- }
-
- if (position == getSelectedItemPosition()) {
- info.setSelected(true);
- info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
- } else {
- info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
- }
-
- if (isClickable()) {
- info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
- info.setClickable(true);
- }
-
- if (isLongClickable()) {
- info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
- info.setLongClickable(true);
- }
-
+ onInitializeAccessibilityNodeInfoForItem(host, position, info);
}
@Override
@@ -2388,6 +2362,45 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with information about a
+ * particular item in the list.
+ *
+ * @param view View representing the list item.
+ * @param position Position of the list item within the adapter.
+ * @param info Node info to populate.
+ */
+ public void onInitializeAccessibilityNodeInfoForItem(
+ View view, int position, AccessibilityNodeInfo info) {
+ final ListAdapter adapter = getAdapter();
+ if (position == INVALID_POSITION || adapter == null) {
+ // The item doesn't exist, so there's not much we can do here.
+ return;
+ }
+
+ if (!isEnabled() || !adapter.isEnabled(position)) {
+ info.setEnabled(false);
+ return;
+ }
+
+ if (position == getSelectedItemPosition()) {
+ info.setSelected(true);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+ } else {
+ info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+ }
+
+ if (isClickable()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+ info.setClickable(true);
+ }
+
+ if (isLongClickable()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+ info.setLongClickable(true);
+ }
+ }
+
void positionSelector(int position, View sel) {
if (position != INVALID_POSITION) {
mSelectorPosition = position;
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index c070ee4..778c8db 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -234,7 +234,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
- info.setOpensPopup(true);
+ info.setCanOpenPopup(true);
}
});
mExpandActivityOverflowButtonImage =
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 6970cde..de2be75 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -1028,26 +1028,29 @@ public class CalendarView extends FrameLayout {
* Sets up the strings to be used by the header.
*/
private void setUpHeader() {
+ final String[] tinyWeekdayNames = LocaleData.get(Locale.getDefault()).tinyWeekdayNames;
mDayLabels = new String[mDaysPerWeek];
- for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
- int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
- mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
- DateUtils.LENGTH_SHORTEST);
+ for (int i = 0; i < mDaysPerWeek; i++) {
+ final int j = i + mFirstDayOfWeek;
+ final int calendarDay = (j > Calendar.SATURDAY) ? j - Calendar.SATURDAY : j;
+ mDayLabels[i] = tinyWeekdayNames[calendarDay];
}
-
+ // Deal with week number
TextView label = (TextView) mDayNamesHeader.getChildAt(0);
if (mShowWeekNumber) {
label.setVisibility(View.VISIBLE);
} else {
label.setVisibility(View.GONE);
}
- for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
- label = (TextView) mDayNamesHeader.getChildAt(i);
+ // Deal with day labels
+ final int count = mDayNamesHeader.getChildCount();
+ for (int i = 0; i < count - 1; i++) {
+ label = (TextView) mDayNamesHeader.getChildAt(i + 1);
if (mWeekDayTextAppearanceResId > -1) {
label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
}
- if (i < mDaysPerWeek + 1) {
- label.setText(mDayLabels[i - 1]);
+ if (i < mDaysPerWeek) {
+ label.setText(mDayLabels[i]);
label.setVisibility(View.VISIBLE);
} else {
label.setVisibility(View.GONE);
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 109fcfe..54cc3f4 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -251,14 +251,14 @@ public class GridLayout extends ViewGroup {
// Instance variables
- final Axis horizontalAxis = new Axis(true);
- final Axis verticalAxis = new Axis(false);
- int orientation = DEFAULT_ORIENTATION;
- boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
- int alignmentMode = DEFAULT_ALIGNMENT_MODE;
- int defaultGap;
- int lastLayoutParamsHashCode = UNINITIALIZED_HASH;
- Printer printer = LOG_PRINTER;
+ final Axis mHorizontalAxis = new Axis(true);
+ final Axis mVerticalAxis = new Axis(false);
+ int mOrientation = DEFAULT_ORIENTATION;
+ boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
+ int mAlignmentMode = DEFAULT_ALIGNMENT_MODE;
+ int mDefaultGap;
+ int mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
+ Printer mPrinter = LOG_PRINTER;
// Constructors
@@ -267,7 +267,7 @@ public class GridLayout extends ViewGroup {
*/
public GridLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- defaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
+ mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout);
try {
setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
@@ -309,7 +309,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_orientation
*/
public int getOrientation() {
- return orientation;
+ return mOrientation;
}
/**
@@ -349,8 +349,8 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_orientation
*/
public void setOrientation(int orientation) {
- if (this.orientation != orientation) {
- this.orientation = orientation;
+ if (this.mOrientation != orientation) {
+ this.mOrientation = orientation;
invalidateStructure();
requestLayout();
}
@@ -369,7 +369,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_rowCount
*/
public int getRowCount() {
- return verticalAxis.getCount();
+ return mVerticalAxis.getCount();
}
/**
@@ -384,7 +384,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_rowCount
*/
public void setRowCount(int rowCount) {
- verticalAxis.setCount(rowCount);
+ mVerticalAxis.setCount(rowCount);
invalidateStructure();
requestLayout();
}
@@ -402,7 +402,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_columnCount
*/
public int getColumnCount() {
- return horizontalAxis.getCount();
+ return mHorizontalAxis.getCount();
}
/**
@@ -417,7 +417,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_columnCount
*/
public void setColumnCount(int columnCount) {
- horizontalAxis.setCount(columnCount);
+ mHorizontalAxis.setCount(columnCount);
invalidateStructure();
requestLayout();
}
@@ -433,7 +433,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
*/
public boolean getUseDefaultMargins() {
- return useDefaultMargins;
+ return mUseDefaultMargins;
}
/**
@@ -463,7 +463,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
*/
public void setUseDefaultMargins(boolean useDefaultMargins) {
- this.useDefaultMargins = useDefaultMargins;
+ this.mUseDefaultMargins = useDefaultMargins;
requestLayout();
}
@@ -480,7 +480,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
public int getAlignmentMode() {
- return alignmentMode;
+ return mAlignmentMode;
}
/**
@@ -499,7 +499,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
public void setAlignmentMode(int alignmentMode) {
- this.alignmentMode = alignmentMode;
+ this.mAlignmentMode = alignmentMode;
requestLayout();
}
@@ -514,7 +514,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
*/
public boolean isRowOrderPreserved() {
- return verticalAxis.isOrderPreserved();
+ return mVerticalAxis.isOrderPreserved();
}
/**
@@ -534,7 +534,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
*/
public void setRowOrderPreserved(boolean rowOrderPreserved) {
- verticalAxis.setOrderPreserved(rowOrderPreserved);
+ mVerticalAxis.setOrderPreserved(rowOrderPreserved);
invalidateStructure();
requestLayout();
}
@@ -550,7 +550,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
public boolean isColumnOrderPreserved() {
- return horizontalAxis.isOrderPreserved();
+ return mHorizontalAxis.isOrderPreserved();
}
/**
@@ -570,7 +570,7 @@ public class GridLayout extends ViewGroup {
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
public void setColumnOrderPreserved(boolean columnOrderPreserved) {
- horizontalAxis.setOrderPreserved(columnOrderPreserved);
+ mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
invalidateStructure();
requestLayout();
}
@@ -581,9 +581,11 @@ public class GridLayout extends ViewGroup {
* @see #setPrinter(android.util.Printer)
*
* @return the printer associated with this view
+ *
+ * @hide
*/
public Printer getPrinter() {
- return printer;
+ return mPrinter;
}
/**
@@ -593,9 +595,11 @@ public class GridLayout extends ViewGroup {
* @param printer the printer associated with this layout
*
* @see #getPrinter()
+ *
+ * @hide
*/
public void setPrinter(Printer printer) {
- this.printer = (printer == null) ? NO_PRINTER : printer;
+ this.mPrinter = (printer == null) ? NO_PRINTER : printer;
}
// Static utility methods
@@ -643,7 +647,7 @@ public class GridLayout extends ViewGroup {
if (c.getClass() == Space.class) {
return 0;
}
- return defaultGap / 2;
+ return mDefaultGap / 2;
}
private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
@@ -651,11 +655,11 @@ public class GridLayout extends ViewGroup {
}
private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
- if (!useDefaultMargins) {
+ if (!mUseDefaultMargins) {
return 0;
}
Spec spec = horizontal ? p.columnSpec : p.rowSpec;
- Axis axis = horizontal ? horizontalAxis : verticalAxis;
+ Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Interval span = spec.span;
boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading;
boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
@@ -672,10 +676,10 @@ public class GridLayout extends ViewGroup {
}
private int getMargin(View view, boolean horizontal, boolean leading) {
- if (alignmentMode == ALIGN_MARGINS) {
+ if (mAlignmentMode == ALIGN_MARGINS) {
return getMargin1(view, horizontal, leading);
} else {
- Axis axis = horizontal ? horizontalAxis : verticalAxis;
+ Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
LayoutParams lp = getLayoutParams(view);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -722,8 +726,8 @@ public class GridLayout extends ViewGroup {
// install default indices for cells that don't define them
private void validateLayoutParams() {
- final boolean horizontal = (orientation == HORIZONTAL);
- final Axis axis = horizontal ? horizontalAxis : verticalAxis;
+ final boolean horizontal = (mOrientation == HORIZONTAL);
+ final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
int major = 0;
@@ -779,9 +783,9 @@ public class GridLayout extends ViewGroup {
}
private void invalidateStructure() {
- lastLayoutParamsHashCode = UNINITIALIZED_HASH;
- horizontalAxis.invalidateStructure();
- verticalAxis.invalidateStructure();
+ mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
+ mHorizontalAxis.invalidateStructure();
+ mVerticalAxis.invalidateStructure();
// This can end up being done twice. Better twice than not at all.
invalidateValues();
}
@@ -789,9 +793,9 @@ public class GridLayout extends ViewGroup {
private void invalidateValues() {
// Need null check because requestLayout() is called in View's initializer,
// before we are set up.
- if (horizontalAxis != null && verticalAxis != null) {
- horizontalAxis.invalidateValues();
- verticalAxis.invalidateValues();
+ if (mHorizontalAxis != null && mVerticalAxis != null) {
+ mHorizontalAxis.invalidateValues();
+ mVerticalAxis.invalidateValues();
}
}
@@ -822,7 +826,7 @@ public class GridLayout extends ViewGroup {
if (span.min != UNDEFINED && span.min < 0) {
handleInvalidParams(groupName + " indices must be positive");
}
- Axis axis = horizontal ? horizontalAxis : verticalAxis;
+ Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
int count = axis.definedCount;
if (count != UNDEFINED) {
if (span.max > count) {
@@ -908,7 +912,7 @@ public class GridLayout extends ViewGroup {
int right = getWidth() - getPaddingRight() - insets.right;
int bottom = getHeight() - getPaddingBottom() - insets.bottom;
- int[] xs = horizontalAxis.locations;
+ int[] xs = mHorizontalAxis.locations;
if (xs != null) {
for (int i = 0, length = xs.length; i < length; i++) {
int x = left + xs[i];
@@ -916,7 +920,7 @@ public class GridLayout extends ViewGroup {
}
}
- int[] ys = verticalAxis.locations;
+ int[] ys = mVerticalAxis.locations;
if (ys != null) {
for (int i = 0, length = ys.length; i < length; i++) {
int y = top + ys[i];
@@ -973,11 +977,11 @@ public class GridLayout extends ViewGroup {
}
private void consistencyCheck() {
- if (lastLayoutParamsHashCode == UNINITIALIZED_HASH) {
+ if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) {
validateLayoutParams();
- lastLayoutParamsHashCode = computeLayoutParamsHashCode();
- } else if (lastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
- printer.println("The fields of some layout parameters were modified in between "
+ mLastLayoutParamsHashCode = computeLayoutParamsHashCode();
+ } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
+ mPrinter.println("The fields of some layout parameters were modified in between "
+ "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
invalidateStructure();
consistencyCheck();
@@ -1005,11 +1009,11 @@ public class GridLayout extends ViewGroup {
if (firstPass) {
measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
} else {
- boolean horizontal = (orientation == HORIZONTAL);
+ boolean horizontal = (mOrientation == HORIZONTAL);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
if (spec.alignment == FILL) {
Interval span = spec.span;
- Axis axis = horizontal ? horizontalAxis : verticalAxis;
+ Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
int[] locations = axis.getLocations();
int cellSize = locations[span.max] - locations[span.min];
int viewSize = cellSize - getTotalMargin(c, horizontal);
@@ -1048,14 +1052,14 @@ public class GridLayout extends ViewGroup {
int heightSansPadding;
// Use the orientation property to decide which axis should be laid out first.
- if (orientation == HORIZONTAL) {
- widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
+ if (mOrientation == HORIZONTAL) {
+ widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
- heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
+ heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
} else {
- heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
+ heightSansPadding = mVerticalAxis.getMeasure(heightSpecSansPadding);
measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
- widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
+ widthSansPadding = mHorizontalAxis.getMeasure(widthSpecSansPadding);
}
int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth());
@@ -1114,11 +1118,11 @@ public class GridLayout extends ViewGroup {
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
- horizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
- verticalAxis.layout(targetHeight - paddingTop - paddingBottom);
+ mHorizontalAxis.layout(targetWidth - paddingLeft - paddingRight);
+ mVerticalAxis.layout(targetHeight - paddingTop - paddingBottom);
- int[] hLocations = horizontalAxis.getLocations();
- int[] vLocations = verticalAxis.getLocations();
+ int[] hLocations = mHorizontalAxis.getLocations();
+ int[] vLocations = mVerticalAxis.getLocations();
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
@@ -1145,8 +1149,8 @@ public class GridLayout extends ViewGroup {
Alignment hAlign = getAlignment(columnSpec.alignment, true);
Alignment vAlign = getAlignment(rowSpec.alignment, false);
- Bounds boundsX = horizontalAxis.getGroupBounds().getValue(i);
- Bounds boundsY = verticalAxis.getGroupBounds().getValue(i);
+ Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i);
+ Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i);
// Gravity offsets: the location of the alignment group relative to its cell group.
int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true));
@@ -1571,7 +1575,7 @@ public class GridLayout extends ViewGroup {
removed.add(arc);
}
}
- printer.println(axisName + " constraints: " + arcsToString(culprits) +
+ mPrinter.println(axisName + " constraints: " + arcsToString(culprits) +
" are inconsistent; permanently removing: " + arcsToString(removed) + ". ");
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index a7d546a..15daf83 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -30,7 +30,10 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
+import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
import android.view.animation.GridLayoutAnimationController;
+import android.widget.AbsListView.LayoutParams;
import android.widget.RemoteViews.RemoteView;
@@ -2259,5 +2262,37 @@ public class GridView extends AbsListView {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(GridView.class.getName());
+
+ final int columnsCount = getNumColumns();
+ final int rowsCount = getCount() / columnsCount;
+ final CollectionInfo collectionInfo = CollectionInfo.obtain(columnsCount, rowsCount, false);
+ info.setCollectionInfo(collectionInfo);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(
+ View view, int position, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
+
+ final int count = getCount();
+ final int columnsCount = getNumColumns();
+ final int rowsCount = count / columnsCount;
+
+ final int row;
+ final int column;
+ if (!mStackFromBottom) {
+ column = position % columnsCount;
+ row = position / columnsCount;
+ } else {
+ final int invertedIndex = count - 1 - position;
+
+ column = columnsCount - 1 - (invertedIndex % columnsCount);
+ row = rowsCount - 1 - invertedIndex / columnsCount;
+ }
+
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
+ final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(column, 1, row, 1, isHeading);
+ info.setCollectionItemInfo(itemInfo);
}
}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 8919248..f2da765 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;
/**
@@ -962,6 +965,33 @@ public class ListPopupWindow {
}
/**
+ * Returns an {@link OnTouchListener} that can be added to the source view
+ * to implement drag-to-open behavior. Generally, the source view should be
+ * the same view that was passed to {@link #setAnchorView}.
+ * <p>
+ * When the listener is set on a view, touching that view and dragging
+ * outside of its bounds will open the popup window. Lifting will select the
+ * currently touched list item.
+ * <p>
+ * Example usage:
+ * <pre>ListPopupWindow myPopup = new ListPopupWindow(context);
+ * myPopup.setAnchor(myAnchor);
+ * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
+ * myAnchor.setOnTouchListener(dragListener);</pre>
+ *
+ * @param src the view on which the resulting listener will be set
+ * @return a touch listener that controls drag-to-open behavior
+ */
+ public OnTouchListener createDragToOpenListener(View src) {
+ return new ForwardingListener(src) {
+ @Override
+ public ListPopupWindow getPopup() {
+ return ListPopupWindow.this;
+ }
+ };
+ }
+
+ /**
* <p>Builds the popup window's content and returns the height the popup
* should have. Returns -1 when the content already exists.</p>
*
@@ -1133,18 +1163,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 +1208,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 +1240,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 +1257,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 +1268,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 +1316,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 +1341,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 +1358,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 +1419,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 +1482,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..941ddfc 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -42,7 +42,8 @@ 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.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
+import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
import android.widget.RemoteViews.RemoteView;
import java.util.ArrayList;
@@ -1507,10 +1508,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 +1562,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 +1684,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 +1729,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;
}
/**
@@ -3816,5 +3780,20 @@ public class ListView extends AbsListView {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(ListView.class.getName());
+
+ final int count = getCount();
+ final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false);
+ info.setCollectionInfo(collectionInfo);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(
+ View view, int position, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoForItem(view, position, info);
+
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
+ final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(0, 1, position, 1, isHeading);
+ info.setCollectionItemInfo(itemInfo);
}
}
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 6a6d767..e5344c6 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -26,6 +26,8 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.ListPopupWindow.ForwardingListener;
/**
* A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
@@ -40,6 +42,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
private MenuPopupHelper mPopup;
private OnMenuItemClickListener mMenuItemClickListener;
private OnDismissListener mDismissListener;
+ private OnTouchListener mDragListener;
/**
* Callback interface used to notify the application that the menu has closed.
@@ -71,6 +74,33 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
+ * Returns an {@link OnTouchListener} that can be added to the anchor view
+ * to implement drag-to-open behavior.
+ * <p>
+ * When the listener is set on a view, touching that view and dragging
+ * outside of its bounds will open the popup window. Lifting will select the
+ * currently touched list item.
+ * <p>
+ * Example usage:
+ * <pre>PopupMenu myPopup = new PopupMenu(context, myAnchor);
+ * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener());</pre>
+ *
+ * @return a touch listener that controls drag-to-open behavior
+ */
+ public OnTouchListener getDragToOpenListener() {
+ if (mDragListener == null) {
+ mDragListener = new ForwardingListener(mAnchor) {
+ @Override
+ public ListPopupWindow getPopup() {
+ return mPopup.getPopup();
+ }
+ };
+ }
+
+ return mDragListener;
+ }
+
+ /**
* @return the {@link Menu} associated with this popup. Populate the returned Menu with
* items before calling {@link #show()}.
*
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 7c7df96..b87ed7a 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;
@@ -675,7 +675,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
info.setClassName(Spinner.class.getName());
if (mAdapter != null) {
- info.setOpensPopup(true);
+ info.setCanOpenPopup(true);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d5fc21c..a2d48a8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4406,6 +4406,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setError(CharSequence error, Drawable icon) {
createEditorIfNeeded();
mEditor.setError(error, icon);
+ notifyViewAccessibilityStateChangedIfNeeded();
}
@Override
@@ -8147,6 +8148,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEditor != null) {
info.setInputType(mEditor.mInputType);
+
+ if (mEditor.mError != null) {
+ info.setContentInvalid(true);
+ }
}
if (!TextUtils.isEmpty(mText)) {
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 1c1d77a..c26cb24 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -443,6 +443,10 @@ public class TimePicker extends FrameLayout {
* Set the current hour.
*/
public void setCurrentHour(Integer currentHour) {
+ setCurrentHour(currentHour, true);
+ }
+
+ private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
// why was Integer used in the first place?
if (currentHour == null || currentHour == getCurrentHour()) {
return;
@@ -463,7 +467,9 @@ public class TimePicker extends FrameLayout {
updateAmPmControl();
}
mHourSpinner.setValue(currentHour);
- onTimeChanged();
+ if (notifyTimeChanged) {
+ onTimeChanged();
+ }
}
/**
@@ -481,8 +487,10 @@ public class TimePicker extends FrameLayout {
mIs24HourView = is24HourView;
getHourFormatData();
updateHourControl();
- // set value after spinner range is updated
- setCurrentHour(currentHour);
+ // set value after spinner range is updated - be aware that because mIs24HourView has
+ // changed then getCurrentHour() is not equal to the currentHour we cached before so
+ // explicitly ask for *not* propagating any onTimeChanged()
+ setCurrentHour(currentHour, false /* no onTimeChanged() */);
updateMinuteControl();
updateAmPmControl();
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index aa94728..8819237 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -22,11 +22,13 @@ import com.android.internal.content.PackageMonitor;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -45,7 +47,6 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
-import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@@ -71,13 +72,13 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
private PackageManager mPm;
private boolean mAlwaysUseOption;
private boolean mShowExtended;
- private GridView mGrid;
+ private ListView mListView;
private Button mAlwaysButton;
private Button mOnceButton;
private int mIconDpi;
private int mIconSize;
private int mMaxColumns;
- private int mLastSelected = GridView.INVALID_POSITION;
+ private int mLastSelected = ListView.INVALID_POSITION;
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -139,17 +140,15 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
finish();
return;
} else if (count > 1) {
- ap.mView = getLayoutInflater().inflate(R.layout.resolver_grid, null);
- mGrid = (GridView) ap.mView.findViewById(R.id.resolver_grid);
- mGrid.setAdapter(mAdapter);
- mGrid.setOnItemClickListener(this);
- mGrid.setOnItemLongClickListener(new ItemLongClickListener());
+ ap.mView = getLayoutInflater().inflate(R.layout.resolver_list, null);
+ mListView = (ListView) ap.mView.findViewById(R.id.resolver_list);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(this);
+ mListView.setOnItemLongClickListener(new ItemLongClickListener());
if (alwaysUseOption) {
- mGrid.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
-
- resizeGrid();
} else if (count == 1) {
startActivity(mAdapter.intentForPosition(0));
mPackageMonitor.unregister();
@@ -172,11 +171,11 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
mAlwaysUseOption = false;
}
}
- }
-
- void resizeGrid() {
- final int itemCount = mAdapter.getCount();
- mGrid.setNumColumns(Math.min(itemCount, mMaxColumns));
+ final int initialHighlight = mAdapter.getInitialHighlight();
+ if (initialHighlight >= 0) {
+ mListView.setItemChecked(initialHighlight, true);
+ onItemClick(null, null, initialHighlight, 0); // Other entries are not used
+ }
}
Drawable getIcon(Resources res, int resId) {
@@ -247,26 +246,26 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (mAlwaysUseOption) {
- final int checkedPos = mGrid.getCheckedItemPosition();
- final boolean enabled = checkedPos != GridView.INVALID_POSITION;
+ final int checkedPos = mListView.getCheckedItemPosition();
+ final boolean enabled = checkedPos != ListView.INVALID_POSITION;
mLastSelected = checkedPos;
mAlwaysButton.setEnabled(enabled);
mOnceButton.setEnabled(enabled);
if (enabled) {
- mGrid.setSelection(checkedPos);
+ mListView.setSelection(checkedPos);
}
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final int checkedPos = mGrid.getCheckedItemPosition();
- final boolean hasValidSelection = checkedPos != GridView.INVALID_POSITION;
+ final int checkedPos = mListView.getCheckedItemPosition();
+ final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
mAlwaysButton.setEnabled(hasValidSelection);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {
- mGrid.smoothScrollToPosition(checkedPos);
+ mListView.smoothScrollToPosition(checkedPos);
}
mLastSelected = checkedPos;
} else {
@@ -276,7 +275,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
public void onButtonClick(View v) {
final int id = v.getId();
- startSelected(mGrid.getCheckedItemPosition(), id == R.id.button_always);
+ startSelected(mListView.getCheckedItemPosition(), id == R.id.button_always);
dismiss();
}
@@ -288,7 +287,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
}
protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
- if (alwaysCheck) {
+ if (mAlwaysUseOption) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
@@ -374,8 +373,19 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
}
- getPackageManager().addPreferredActivity(filter, bestMatch, set,
- intent.getComponent());
+ if (alwaysCheck) {
+ getPackageManager().addPreferredActivity(filter, bestMatch, set,
+ intent.getComponent());
+ } else {
+ try {
+ AppGlobals.getPackageManager().setLastChosenActivity(intent,
+ intent.resolveTypeIfNeeded(getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ filter, bestMatch, intent.getComponent());
+ } catch (RemoteException re) {
+ Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
+ }
+ }
}
}
@@ -410,11 +420,13 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
private final class ResolveListAdapter extends BaseAdapter {
private final Intent[] mInitialIntents;
private final List<ResolveInfo> mBaseResolveList;
+ private ResolveInfo mLastChosen;
private final Intent mIntent;
private final int mLaunchedFromUid;
private final LayoutInflater mInflater;
private List<DisplayResolveInfo> mList;
+ private int mInitialHighlight = -1;
public ResolveListAdapter(Context context, Intent intent,
Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid) {
@@ -436,14 +448,24 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
if (newItemCount == 0) {
// We no longer have any items... just finish the activity.
finish();
- } else if (newItemCount != oldItemCount) {
- resizeGrid();
}
}
+ public int getInitialHighlight() {
+ return mInitialHighlight;
+ }
+
private void rebuildList() {
List<ResolveInfo> currentResolveList;
+ try {
+ mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
+ mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY);
+ } catch (RemoteException re) {
+ Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
+ }
+
mList.clear();
if (mBaseResolveList != null) {
currentResolveList = mBaseResolveList;
@@ -556,6 +578,12 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
// Process labels from start to i
int num = end - start+1;
if (num == 1) {
+ if (mLastChosen != null
+ && mLastChosen.activityInfo.packageName.equals(
+ ro.activityInfo.packageName)
+ && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) {
+ mInitialHighlight = mList.size();
+ }
// No duplicate labels. Use label for entry at start
mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
} else {
@@ -585,6 +613,12 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
}
for (int k = start; k <= end; k++) {
ResolveInfo add = rList.get(k);
+ if (mLastChosen != null
+ && mLastChosen.activityInfo.packageName.equals(
+ add.activityInfo.packageName)
+ && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) {
+ mInitialHighlight = mList.size();
+ }
if (usePkg) {
// Use application name for all entries from start to end-1
mList.add(new DisplayResolveInfo(add, roLabel,
diff --git a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java b/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
index 62529e9..f484724 100644
--- a/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
+++ b/core/java/com/android/internal/notification/DemoContactNotificationScorer.java
@@ -41,8 +41,8 @@ import java.util.List;
*/
public class DemoContactNotificationScorer implements NotificationScorer {
- private static final String TAG = "StarredContactScoring";
- private static final boolean DBG = true;
+ private static final String TAG = "DemoContactNotificationScorer";
+ private static final boolean DBG = false;
protected static final boolean ENABLE_CONTACT_SCORER = true;
private static final String SETTING_ENABLE_SCORER = "contact_scorer_enabled";
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index cdd2ad1..a85d5fe 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -334,6 +334,7 @@ public class RuntimeInit {
}
} catch (Throwable t2) {
Slog.e(TAG, "Error reporting WTF", t2);
+ Slog.e(TAG, "Original WTF:", t);
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index 5d0a603..f060efd 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) {
@@ -630,7 +630,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setOpensPopup(true);
+ info.setCanOpenPopup(true);
}
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 85d9cbd..a2a4acc 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -276,7 +276,7 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
super.onInitializeAccessibilityNodeInfo(info);
if (mItemData != null && mItemData.hasSubMenu()) {
- info.setOpensPopup(true);
+ info.setCanOpenPopup(true);
}
}
}
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..afa4103
--- /dev/null
+++ b/core/java/com/android/internal/widget/AutoScrollHelper.java
@@ -0,0 +1,924 @@
+/*
+ * 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 #scrollTargetBy}, {@link #canTargetScrollHorizontally}, and
+ * {@link #canTargetScrollVertically} methods. 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 #scrollTargetBy} 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 2500 milliseconds.
+ * <li>Acceleration ramp-down duration, see {@link #setRampDownDuration}.
+ * Default value is 500 milliseconds.
+ * <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 mAlreadyDelayed;
+
+ /** Whether to reset the scroller start time on the next animation. */
+ private boolean mNeedsReset;
+
+ /** Whether to send a cancel motion event to the target view. */
+ private boolean mNeedsCancel;
+
+ /** Whether the auto-scroller is actively scrolling. */
+ private boolean mAnimating;
+
+ /** Whether the auto-scroller is enabled. */
+ private boolean mEnabled;
+
+ /** Whether the auto-scroller consumes events when scrolling. */
+ private boolean mExclusive;
+
+ // 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;
+ private static final int DEFAULT_RAMP_DOWN_DURATION = 500;
+
+ /**
+ * 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);
+ setRampDownDuration(DEFAULT_RAMP_DOWN_DURATION);
+ }
+
+ /**
+ * 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 (mEnabled && !enabled) {
+ requestStop();
+ }
+
+ 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 exclusive True to exclusively handle touch events during scrolling,
+ * false to allow the target view to receive all touch events.
+ * @return The scroll helper, which may used to chain setter calls.
+ */
+ public AutoScrollHelper setExclusive(boolean exclusive) {
+ mExclusive = exclusive;
+ return this;
+ }
+
+ /**
+ * 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 #setExclusive(boolean)
+ */
+ public boolean isExclusive() {
+ return mExclusive;
+ }
+
+ /**
+ * 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.setRampUpDuration(durationMillis);
+ return this;
+ }
+
+ /**
+ * Sets the amount of time after de-activation of auto-scrolling that is
+ * takes to slow to a stop.
+ * <p>
+ * Specifying a duration greater than zero prevents sudden jumps in
+ * velocity.
+ *
+ * @param durationMillis The ramp-down duration in milliseconds.
+ * @return The scroll helper, which may used to chain setter calls.
+ */
+ public AutoScrollHelper setRampDownDuration(int durationMillis) {
+ mScroller.setRampDownDuration(durationMillis);
+ return this;
+ }
+
+ /**
+ * Handles touch events by activating automatic scrolling, adjusting scroll
+ * velocity, or stopping.
+ * <p>
+ * If {@link #isExclusive()} 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:
+ mNeedsCancel = true;
+ mAlreadyDelayed = false;
+ // $FALL-THROUGH$
+ case MotionEvent.ACTION_MOVE:
+ final float xTargetVelocity = computeTargetVelocity(
+ HORIZONTAL, event.getX(), v.getWidth(), mTarget.getWidth());
+ final float yTargetVelocity = computeTargetVelocity(
+ VERTICAL, event.getY(), v.getHeight(), mTarget.getHeight());
+ mScroller.setTargetVelocity(xTargetVelocity, yTargetVelocity);
+
+ // If the auto scroller was not previously active, but it should
+ // be, then update the state and start animations.
+ if (!mAnimating && shouldAnimate()) {
+ startAnimating();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ requestStop();
+ break;
+ }
+
+ return mExclusive && mAnimating;
+ }
+
+ /**
+ * @return whether the target is able to scroll in the requested direction
+ */
+ private boolean shouldAnimate() {
+ final ClampedScroller scroller = mScroller;
+ final int verticalDirection = scroller.getVerticalDirection();
+ final int horizontalDirection = scroller.getHorizontalDirection();
+
+ return verticalDirection != 0 && canTargetScrollVertically(verticalDirection)
+ || horizontalDirection != 0 && canTargetScrollHorizontally(horizontalDirection);
+ }
+
+ /**
+ * Starts the scroll animation.
+ */
+ private void startAnimating() {
+ if (mRunnable == null) {
+ mRunnable = new ScrollAnimationRunnable();
+ }
+
+ mAnimating = true;
+ mNeedsReset = true;
+
+ if (!mAlreadyDelayed && mActivationDelay > 0) {
+ mTarget.postOnAnimationDelayed(mRunnable, mActivationDelay);
+ } else {
+ mRunnable.run();
+ }
+
+ // If we start animating again before the user lifts their finger, we
+ // already know it's not a tap and don't need an activation delay.
+ mAlreadyDelayed = true;
+ }
+
+ /**
+ * Requests that the scroll animation slow to a stop. If there is an
+ * activation delay, this may occur between posting the animation and
+ * actually running it.
+ */
+ private void requestStop() {
+ if (mNeedsReset) {
+ // The animation has been posted, but hasn't run yet. Manually
+ // stopping animation will prevent it from running.
+ mAnimating = false;
+ } else {
+ mScroller.requestStop();
+ }
+ }
+
+ private float computeTargetVelocity(
+ int direction, float coordinate, float srcSize, float dstSize) {
+ final float relativeEdge = mRelativeEdges[direction];
+ final float maximumEdge = mMaximumEdges[direction];
+ final float value = getEdgeValue(relativeEdge, srcSize, maximumEdge, coordinate);
+ if (value == 0) {
+ // The edge in this direction is not activated.
+ return 0;
+ }
+
+ final float relativeVelocity = mRelativeVelocity[direction];
+ final float minimumVelocity = mMinimumVelocity[direction];
+ final float maximumVelocity = mMaximumVelocity[direction];
+ final float targetVelocity = relativeVelocity * dstSize;
+
+ // Target velocity is adjusted for interpolated edge position, then
+ // clamped to the minimum and maximum values. Later, this value will be
+ // adjusted for time-based acceleration.
+ if (value > 0) {
+ return constrain(value * targetVelocity, minimumVelocity, maximumVelocity);
+ } else {
+ return -constrain(-value * targetVelocity, minimumVelocity, maximumVelocity);
+ }
+ }
+
+ /**
+ * Override this method to scroll the target view by the specified number of
+ * pixels.
+ *
+ * @param deltaX The number of pixels to scroll by horizontally.
+ * @param deltaY The number of pixels to scroll by vertically.
+ */
+ public abstract void scrollTargetBy(int deltaX, int deltaY);
+
+ /**
+ * Override this method to return whether the target view can be scrolled
+ * horizontally in a certain direction.
+ *
+ * @param direction Negative to check scrolling left, positive to check
+ * scrolling right.
+ * @return true if the target view is able to horizontally scroll in the
+ * specified direction.
+ */
+ public abstract boolean canTargetScrollHorizontally(int direction);
+
+ /**
+ * Override this method to return whether the target view can be scrolled
+ * vertically in a certain direction.
+ *
+ * @param direction Negative to check scrolling up, positive to check
+ * scrolling down.
+ * @return true if the target view is able to vertically scroll in the
+ * specified direction.
+ */
+ public abstract boolean canTargetScrollVertically(int direction);
+
+ /**
+ * 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 (mAnimating && (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 int constrain(int value, int min, int max) {
+ if (value > max) {
+ return max;
+ } else if (value < min) {
+ return min;
+ } else {
+ return value;
+ }
+ }
+
+ private static float constrain(float value, float min, float max) {
+ if (value > max) {
+ return max;
+ } else if (value < min) {
+ return min;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Sends a {@link MotionEvent#ACTION_CANCEL} event to the target view,
+ * canceling any ongoing touch events.
+ */
+ private void cancelTargetTouch() {
+ final long eventTime = SystemClock.uptimeMillis();
+ final MotionEvent cancel = MotionEvent.obtain(
+ eventTime, eventTime, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ mTarget.onTouchEvent(cancel);
+ cancel.recycle();
+ }
+
+ private class ScrollAnimationRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (!mAnimating) {
+ return;
+ }
+
+ if (mNeedsReset) {
+ mNeedsReset = false;
+ mScroller.start();
+ }
+
+ final ClampedScroller scroller = mScroller;
+ if (scroller.isFinished() || !shouldAnimate()) {
+ mAnimating = false;
+ return;
+ }
+
+ if (mNeedsCancel) {
+ mNeedsCancel = false;
+ cancelTargetTouch();
+ }
+
+ scroller.computeScrollDelta();
+
+ final int deltaX = scroller.getDeltaX();
+ final int deltaY = scroller.getDeltaY();
+ scrollTargetBy(deltaX, deltaY);
+
+ // Keep going until the scroller has permanently stopped.
+ mTarget.postOnAnimation(this);
+ }
+ }
+
+ /**
+ * 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 int mRampUpDuration;
+ private int mRampDownDuration;
+ private float mTargetVelocityX;
+ private float mTargetVelocityY;
+
+ private long mStartTime;
+
+ private long mDeltaTime;
+ private int mDeltaX;
+ private int mDeltaY;
+
+ private long mStopTime;
+ private float mStopValue;
+ private int mEffectiveRampDown;
+
+ /**
+ * Creates a new ramp-up scroller that reaches full velocity after a
+ * specified duration.
+ */
+ public ClampedScroller() {
+ mStartTime = Long.MIN_VALUE;
+ mStopTime = -1;
+ mDeltaTime = 0;
+ mDeltaX = 0;
+ mDeltaY = 0;
+ }
+
+ public void setRampUpDuration(int durationMillis) {
+ mRampUpDuration = durationMillis;
+ }
+
+ public void setRampDownDuration(int durationMillis) {
+ mRampDownDuration = durationMillis;
+ }
+
+ /**
+ * Starts the scroller at the current animation time.
+ */
+ public void start() {
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStopTime = -1;
+ mDeltaTime = mStartTime;
+ mStopValue = 0.5f;
+ mDeltaX = 0;
+ mDeltaY = 0;
+ }
+
+ /**
+ * Stops the scroller at the current animation time.
+ */
+ public void requestStop() {
+ final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mEffectiveRampDown = constrain((int) (currentTime - mStartTime), 0, mRampDownDuration);
+ mStopValue = getValueAt(currentTime);
+ mStopTime = currentTime;
+ }
+
+ public boolean isFinished() {
+ return mStopTime > 0
+ && AnimationUtils.currentAnimationTimeMillis() > mStopTime + mEffectiveRampDown;
+ }
+
+ private float getValueAt(long currentTime) {
+ if (currentTime < mStartTime) {
+ return 0f;
+ } else if (mStopTime < 0 || currentTime < mStopTime) {
+ final long elapsedSinceStart = currentTime - mStartTime;
+ return 0.5f * constrain(elapsedSinceStart / (float) mRampUpDuration, 0, 1);
+ } else {
+ final long elapsedSinceEnd = currentTime - mStopTime;
+ return (1 - mStopValue) + mStopValue
+ * constrain(elapsedSinceEnd / (float) mEffectiveRampDown, 0, 1);
+ }
+ }
+
+ /**
+ * Interpolates the value along a parabolic curve corresponding to the equation
+ * <code>y = -4x * (x-1)</code>.
+ *
+ * @param value The value to interpolate, between 0 and 1.
+ * @return the interpolated value, between 0 and 1.
+ */
+ private float interpolateValue(float value) {
+ return -4 * value * value + 4 * value;
+ }
+
+ /**
+ * Computes the current scroll deltas. This usually only be called after
+ * starting the scroller with {@link #start()}.
+ *
+ * @see #getDeltaX()
+ * @see #getDeltaY()
+ */
+ public void computeScrollDelta() {
+ if (mDeltaTime == 0) {
+ throw new RuntimeException("Cannot compute scroll delta before calling start()");
+ }
+
+ final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ final float value = getValueAt(currentTime);
+ final float scale = interpolateValue(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;
+ }
+
+ public int getHorizontalDirection() {
+ return (int) (mTargetVelocityX / Math.abs(mTargetVelocityX));
+ }
+
+ public int getVerticalDirection() {
+ return (int) (mTargetVelocityY / Math.abs(mTargetVelocityY));
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * An implementation of {@link AutoScrollHelper} that knows how to scroll
+ * through an {@link AbsListView}.
+ */
+ public static class AbsListViewAutoScroller extends AutoScrollHelper {
+ private final AbsListView mTarget;
+
+ public AbsListViewAutoScroller(AbsListView target) {
+ super(target);
+
+ mTarget = target;
+ }
+
+ @Override
+ public void scrollTargetBy(int deltaX, int deltaY) {
+ mTarget.scrollListBy(deltaY);
+ }
+
+ @Override
+ public boolean canTargetScrollHorizontally(int direction) {
+ // List do not scroll horizontally.
+ return false;
+ }
+
+ @Override
+ public boolean canTargetScrollVertically(int direction) {
+ final AbsListView target = mTarget;
+ final int itemCount = target.getCount();
+ final int childCount = target.getChildCount();
+ final int firstPosition = target.getFirstVisiblePosition();
+ final int lastPosition = firstPosition + childCount;
+
+ if (direction > 0) {
+ // Are we already showing the entire last item?
+ if (lastPosition >= itemCount) {
+ final View lastView = target.getChildAt(childCount - 1);
+ if (lastView.getBottom() <= target.getHeight()) {
+ return false;
+ }
+ }
+ } else if (direction < 0) {
+ // Are we already showing the entire first item?
+ if (firstPosition <= 0) {
+ final View firstView = target.getChildAt(0);
+ if (firstView.getTop() >= 0) {
+ return false;
+ }
+ }
+ } else {
+ // The behavior for direction 0 is undefined and we can return
+ // whatever we want.
+ return false;
+ }
+
+ return true;
+ }
+ }
+}